How to Fix the Elixir Error: Erlang Error Guard_expr
While experimenting in IEx, I stumbled upon a confusing error that didn’t make sense at first. I wrote what I thought was harmless code:
case %{} do
x when x == %{} -> true
_x -> false
end
And then boom
** (ErlangError) erlang error: :guard_expr
At first, I assumed I messed up my syntax. But no this error was deeper. So I decided to dig in and what I found taught me something fundamental about how Elixir guards work.
Why This Error Happens
The root cause is surprisingly simple:
Elixir guards only allow a limited set of expressions.x == %{} inside a when clause is NOT allowed, because you can’t compare against a map in guards.
Guards only allow comparisons with simple values like x == 1 or x == nil, but map literals (%{}) and complex data types are forbidden.
So Elixir raises:
erlang error: :guard_expr → “Invalid expression inside a guard.”
Correct Version
Instead of forcing %{} inside a guard, I can directly match it as a pattern:
case %{} do
%{} -> true
_ -> false
end
Why does this work
Because pattern matching is allowed, even with complex structures like maps.
But guards are more strict, and %{} is not allowed inside them.
My Next Experiment A Custom || Macro
I wanted to be clever and build my own version of the || operator one where empty lists ([]), empty tuples ({}), and empty maps (%{}) also behaved like false.
So I tried:
defmodule Or do
defmacro left || right do
quote do
case unquote(left) do
x when x in [false, nil] or x == [] or x == {} or x == %{} ->
unquote(right)
x ->
x
end
end
end
end
Guess what? SAME ERROR. Removing or x == %{} suddenly made it work.
At that point, I knew %{} is illegal inside guards. Period.
Final Fix My Working Custom || Macro
The solution? Avoid guards entirely. Use raw pattern matching instead.
defmodule Or do
defmacro left || right do
quote do
case unquote(left) do
false -> unquote(right)
nil -> unquote(right)
[] -> unquote(right)
{} -> unquote(right)
%{} -> unquote(right)
x -> x
end
end
end
end
Now it behaves almost exactly like ||, except with extra falsy values:
import Or
[] || "fallback" #=> "fallback"
%{} || "default" #=> "default"
5 || "ignored" #=> 5
Practice Challenges
Try extending your skills:
- Write a
truthy?/1function that returnstrueorfalsebased on custom falsy rules (false,nil,[],{},%{}). - Modify the macro to also treat
""(empty string) as falsey. - Write an
&¯o equivalent that mirrors the same logic.