match evaluates a subject and selects a matching arm.
label := match status {
Pending => "pending"
Running => "running"
Complete => "complete"
}The subject is evaluated once. Arms are considered in source order.
Selection proceeds in three stages:
- evaluate the subject;
- test an arm’s structural pattern and create its bindings;
- evaluate that arm’s guard, when present.
The first arm passing all applicable stages wins. Later arms do not run.
Arms
An arm contains a pattern, optional guard, =>, and a body.
result := match message {
Data(value) => transform(value)
Flush => {
flush()
default_value()
}
}The body can be one expression or a block. Pattern bindings are visible in the guard and body but not in other arms.
Separate arms do not require commas. A comma after a short expression arm is accepted where the grammar permits one, but the book uses line-separated arms consistently.
Values
A match is an expression. Every reachable, normally completing arm must produce a compatible value.
code := match response {
Success(_) => 200
Missing => 404
Failure(_) => 500
}A returning or error-propagating arm diverges and therefore does not need to produce the common result type.
value := match result {
Ok(value) => value
Err(error) => return handle(error)
}Guards
An if guard adds a boolean requirement after structural matching.
category := match value {
number if number < 0 => "negative"
0 => "zero"
_ => "positive"
}The pattern runs first, then the guard. If the guard is false, matching continues with later arms.
Guards generally do not make a variant exhaustive because arbitrary boolean logic cannot prove that every value satisfies one. Include an unguarded fallback where needed.
Side effects in a guard occur only after its pattern matched. If the guard is false, those effects have already happened before selection continues. Keep guards observational and move substantial work into the arm body.
Exhaustiveness
A match over a closed domain must cover every reachable case.
fn present(value: Option[int]): int {
return match value {
Some(number) => number
None => 0
}
}Closed domains include booleans, enums, Option, Result, and known anonymous
unions. Integers, strings, and other open-ended values need a wildcard or a
provably complete pattern set.
The compiler reports:
- missing cases in a non-exhaustive match;
- unreachable arms already covered by earlier patterns;
- incompatible pattern types;
- inconsistent bindings across alternative patterns.
An alternative pattern must bind the same names with compatible types on every side:
enum Lookup {
Loaded(string)
Cached(string)
Missing
}
label := match lookup {
Loaded(value) | Cached(value) => value
Missing => "unavailable"
}If only one alternative binds value, the body could not use it safely and
the pattern is rejected.
Because arms are ordered, a broad pattern can make a later specific arm unreachable:
match status {
_ => use_default()
Ready => start() // unreachable
}Place wildcards and whole-type group patterns after the cases they are meant to leave visible.
Unions
Matching an anonymous union refines the subject to the selected constituent.
fn describe(value: int | string): string {
return match value {
number: int => "number: $number"
text: string => "text: $text"
}
}When two constituent enums expose a variant with the same name, qualify the variant or match the constituent type group.
Error unions use the same machinery and are covered in Error Unions.
Subjectless form
A subjectless match checks boolean arms in order.
message := match {
temperature < 0 => "freezing"
temperature < 20 => "cold"
_ => "warm"
}This is useful for a flat decision table. Prefer if when there are only two
branches or when nested conditions communicate the logic more clearly.
In the subjectless form each arm pattern is a boolean expression. It is still
ordered and requires a final _ when earlier conditions do not cover every
possibility.
Constants
A visible constant in a pattern matches its value.
const HTTP_OK = 200
match status {
HTTP_OK => success()
400..=499 => client_error()
_ => server_error()
}The compiler distinguishes a resolved constant from a new pattern binding.
Literal and range arms use value equality and range membership. They do not convert the subject to another numeric or string type merely to make a pattern fit.
Ownership
Pattern matching follows the subject type’s value and reference semantics. Destructuring does not grant mutation by itself. Bind through a mutable place or use methods whose receiver contract permits mutation when an arm needs to update shared state.