Welcome back. In the previous lesson, you used multiple function clauses to match different input shapes right in the function head. As a reminder, we also briefly used a guard to prevent dividing by zero. In this lesson, you will focus on guard clauses themselves — how to attach conditions to a clause so it runs only when certain checks (type, range, etc.) are true.
Explanation:
- The module defines three clauses of the same function,
validate_age/1. Elixir tries them from top to bottom and picks the first one whose pattern matches and whose guard evaluates totrue. - First clause: applies only when
ageis an integer and between 0 and 17. It returns{:ok, "Minor"}. - Second clause: applies only when
ageis an integer and at least 18. It returns{:ok, "Adult"}. - Third clause: no guard, catches everything else — negative numbers, non-integers, etc. The parameter is named
_ageto signal it is intentionally ignored. It returns{:error, "Invalid age"}. - The three
IO.inspectcalls show:15->{:ok, "Minor"}25->{:ok, "Adult"}
- Type checks:
is_atom/1,is_binary/1,is_bitstring/1,is_boolean/1,is_float/1,is_function/1,2,is_integer/1,is_list/1,is_map/1,is_nil/1,is_number/1,is_pid/1,is_port/1,is_reference/1,is_tuple/1 - Comparisons:
==,!=,===,!==,<,<=, , , and the operator (with lists/ranges)
- Use
and,or,notin guards. The operators&&,||,!are not allowed in guard expressions. - Precedence:
and/orhave lower precedence than comparisons and arithmetic. Use parentheses in complex guards for readability.
Example:
Elixir evaluates clauses from top to bottom. If the pattern matches but the guard fails, it proceeds to the next clause.
-
Checking list emptiness:
- Instead of
Enum.empty?(list)(not allowed), pattern-match the shape:
- Instead of
-
Checking a map has a key:
- Instead of
Map.has_key?(m, :age)(not allowed), match the map shape:
- Instead of
Compose domain checks once and reuse them across clauses.
You can also use guard macros in other guard-capable constructs (see below).
Guards work in case, receive, and with expressions.
-
In
case: -
In
receive: -
In
with(guards on patterns in generators/clauses):
Differences: How They Handle Guard Failure
While the syntax for guards is the same across these constructs, how they behave when a guard fails differs:
case: If a pattern matches but the guard fails, Elixir tries the next clause. If no clauses match, aCaseClauseErroris raised.receive: If a guard fails, the message is not consumed. It remains in the process mailbox, and Elixir looks for the next message that might match.
Today you learned how to:
- Refine matches: Use
whento attach type and range checks to function heads and control flow structures. - Stay "Guard-Safe": Only use allowed operations like
is_integer/1andand/or/notto avoid compilation errors. - Manage logic: Handle overlapping conditions using top-to-bottom ordering and
defguardfor reusability. - Differentiate context: Understand how failures vary between
case(errors),receive(skips), andwith(halts).
Ready to apply it? Head to the practice section and put guard clauses to work.
