guards in Elixir 1.6

Elixir 1.6 has been released with a lot of nice additions to the language, but without a doubt, what I’m most pumped about is real guard support in the language.

Before 1.6, you could constraint a function like so:

def change_settings(%User{admin: is_admin}, settings) when is_admin == true do
    # Update the settings is only available for admins
end

This worked just fine, but now with real guard support, we can extract those checks to make reuse and composition easier!

defmodule User do
  defstruct [:karma, :friend_count]
end

defmodule Main do
  defmacro can_update_settings(karma, friend_count) do
    quote do
      unquote(karma)> 100 and unquote(friend_count) > 3
    end
  end

  def change_settings(user = %User{karma: karma, friend_count: count}, settings) when can_update_settings(karma, count) do
    IO.puts "Updating settings..."

    {:ok, user}
  end

  def add_member(user = %User{karma: karma, friend_count: count}, new_user) when can_update_settings(karma, count) do
    IO.puts "Adding member"

    {:ok, user}
  end

  def main do
    %User{karma: 101, friend_count: 4}
    |> change_settings(%{})
  end
end

Main.main

// => "Updating settings..."

You can skip the manual macro creation alltogether if you use defguard and defguardp, though. These are new functions in the Kernel module that just take your constraint and generate a macro suitable for use as a guard from it.

defguard can_update_settings(verified) when verified == true

I specially love the fact that now you can also use multiple guards with functions:

defmodule User do
  defstruct [:karma, :friend_count]
end

defmodule CommunityFactory do
  defguard has_enough_karma(karma) when karma > 100
  defguard has_invited_enough_friends(friend_count) when friend_count > 3

  def create_community(%User{karma: karma, friend_count: count}, com_name)
    when has_enough_karma(karma)
    when has_invited_enough_friends(count) do

    IO.puts "Creating community..."

    {:ok, %{name: com_name}}
  end

  def create_community(_user, com_name) do
    IO.puts "Cannot create #{com_name}. User still does not complete onboarding."

    {:error, nil}
  end

end

defmodule Main do
  def main do
    %User{karma: 101, friend_count: 5}
    |> CommunityFactory.create_community("My Community")
  end
end

Main.main

// => Creating community...

I use guards like crazy in Swift. I think they’re a great tool to structure your code. However, in Swift, guards can be used (and maybe abused) more as a fancy if/else dance, rather than a mean to express condition conformance.

For instance, in Swift, the only requirement for a guard is that it breaks the flow of the function:

guard user.isAdmin else { 
    print("User is not admin!")  # Compiling error, does not break function flow
}

// Update settings here

But literally nothing stops you from transferring the control over to another function!

guard user.isAdmin else {
    showErrorMessage(.notAdmin) // Give up control of the execution flow
    return
}

// Update settings here

Although this may seem harmless, it can cost some precious down the road when you’re trying to debug something that just follows a trail of guarded statements that keep on going forever.

With Elixir’s flavor of guards, this is explicitly not possible:

Not all expressions are allowed in guard clauses, but only a handful of them. This is a deliberate choice: only a predefined set of side-effect-free functions are allowed. This way, Elixir (and Erlang) can make sure that nothing bad happens while executing guards and no mutations happen anywhere.

Altough I love Swift, I do think that its guard does not entirely guard you from shooting yourself in the foot.


Small but thoughtful updates like this to the language really make me hopeful for the future of Elixir.

You can read everything about the new Elixir guards on the official documentation page and read the release announcement here.