Pattern matching
match value {
0 => "zero"
-1 => "negative one"
n when n > 0 => "positive"
_ => "negative"
}
An expression: it evaluates to the matched arm.
What you can match
Literals, including b"\x01" byte literals.
Guards with when, evaluated with the arm's bindings in scope:
n when n > 0 => "positive"
Maps and structs, by the subset of fields you name:
match user {
{ role: "admin" } => "admin access"
{ role: "user", active: true } => "active user"
_ => "unknown"
}
Variants, positionally by field order:
type Shape = Circle { r: Int } | Square { side: Int }
match shape {
Circle(r) => 3.14159 * r * r
Square(side) => side * side
}
_ matches anything.
Bindings are arm-scoped
A name bound by a pattern exists only inside that arm. It cannot leak into or clobber the surrounding scope, so reusing a familiar name in a pattern is safe.
Exhaustiveness is checked
ecko check reports a match over a declared type that is missing a variant and has no _, before the program runs. Adding a variant and forgetting one of its match sites is found at analysis time.
This is the main reason to declare a type rather than passing strings around: a string typo is a runtime surprise, a missing variant arm is a compile-time error.
Matching tests, it never accesses
A pattern that does not match is not an error - it moves to the next arm. That is the difference from strict access: m.role on a map without role raises, while { role: "admin" } as a pattern simply does not match.
So match is the natural tool for data whose shape you are unsure of, including decoded JSON and model output.
With ai
A typed call returning an enum drops straight into an exhaustive match:
type Sentiment = Positive | Negative | Neutral
match ai[Sentiment] "classify: {review}" {
Positive => thank(review)
Negative => escalate(review)
Neutral => file(review)
}
The model is constrained to the variants and the branches are checked. This is the pattern the language is shaped around - a model call whose result is as trustworthy as a local function's.
Built-in unions
Ok/Err and Some/None are predefined for your own modelling. They are not the error channel - see Error handling.