Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Pattern matching

match-expr ::= 'match' expr '{' arm (',' …)* ','? '}'
arm        ::= pattern ('if' expr)? '=>' expr
pattern    ::= '_'
            |  literal
            |  Ident                                    // binder
            |  TypeExpr '(' pattern (',' …)* ')'        // variant
            |  TypeExpr '{' field-pattern-list '}'      // record
            |  Ident ':' TypeExpr                       // type test
            |  'is' reasoning-outcome
            |  pattern '|' pattern                      // OR
reasoning-outcome ::= 'unknown'
                  |  'ambiguous' '(' Ident ')'
                  |  'timeout'   '(' Ident ')'
                  |  'both'      '(' Ident ',' Ident ')'
pub fn classify(l: Lease) -> Status =
    match l {
        ResidentialLease(r) if r.bedrooms > 4 => Status::Large,
        ResidentialLease(_)                   => Status::Standard,
        CommercialLease(_)                    => Status::Commercial,
        _: TerminatedLease                    => Status::Closed,
        is unknown                            => Status::Pending,
        is ambiguous(x)                       => Status::Ambiguous(x),
        _                                     => Status::Unknown,
    };

Exhaustiveness checked; non-exhaustive matches require _ or fail with OE0203.

Match semantics

match evaluates — in value position and statement position — as ordered first-match over constant patterns:

Pattern formAdmittedNotes
wildcard _Matches everything.
literal (Int / String / Bool / Date)Value equality (Int widens per Engine / Module / Store factoring).
payloadless enum constant path (Status::Active)Resolved through the enum resolver; canonical-CBOR equality at runtime.
or-pattern of the above (A | B => v)Expands to consecutive arms sharing the body.
binder Identrefused OE1319
variant payload Type(p) (one positional binder)✓ value positionBinds the payload over the arm body; refused OE1319 in a statement-position effectful arm (below). A zero- or multi-binder arm is OE0257; the record form Type { … } is refused.
type test x: Trefused OE1319
is reasoning-outcomerefused OE1319
arm guard pat if condrefused OE1319

A value-position match (a fn body, a rule-body comparison operand, a mutate let RHS / update … set value / return value / require guard) desugars at elaboration to a right-nested conditional chain over scrutinee == constant tests — if s == P1 { v1 } else if s == P2 { v2 } else { v_last } — so it executes on the existing IfExpr paths in the reasoner and the mutate runtime; semantics are first-match in arm order, and the final arm’s value is the else-branch. Exhaustiveness is required: a final _ arm, or full coverage of the scrutinee’s enum variants when statically known — otherwise OE0203 at ox check. Statement-position match (arms running effects inside a mutate body) executes with the same semantics (RFD 0015 Amendment 2): the arms are blocks running mutate statements (update / insert / delete / forget / require / nested for / if / match), the match lowers to a right-nested Operation::If chain over the arm blocks’ operations — ordered first-match, exactly one selected arm’s effects run, the arm value is discarded — and the same exhaustiveness rule applies (_ => {} with an empty block is the idiomatic catch-all; OE0203 otherwise). Or-patterns expand to consecutive conditions sharing the arm’s operations.

A single-binder payload destructuring (Some(v) => …) executes in value positionlet r = match opt { Some(v) => v, None => 0 }; and every value-position site above: the arm’s condition tests the variant tag and its body sees the binder substituted by the decoded payload. A payload-binding arm in statement position (an effectful arm inside a mutate statement-match) refuses with OE1319: bind the payload in value position first (let v = match opt { … };), then run the effect. A bare binder Ident, a type test, an is-outcome arm, and an arm guard refuse with OE1319 in both positions.

Rule-body arm projections must be total. In a rule body (derive / check / query / bridge), the relational lowering hoists every field projection inside the conditional chain — including arms the scrutinee never selects — into an eager, unconditional $field:: join, because the relational plan has no lazy join. $field::<f> has no row when an individual lacks f, so an arm projecting an optional (T?) field would silently filter out every row missing that field, even rows whose matching arm never touches it. Rather than mis-evaluate, ox check / ox build refuse such a rule with OE1320: every field projected inside a conditional arm (a value-if branch or match arm — both lower identically) must be total, i.e. declared required on every visible type that declares it. Workarounds: split the conditional into one rule per arm (each joining only the fields that arm needs), or declare the field required. Projections in always-evaluated positions (the rule body proper, the outermost condition / scrutinee test) and fn / mutate bodies (which evaluate branches lazily) are unaffected.