Skip to content

Match

Select guarded cases, produce values, refine unions, and satisfy exhaustiveness.

Updated View as Markdown

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:

  1. evaluate the subject;
  2. test an arm’s structural pattern and create its bindings;
  3. 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.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close