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 form | Admitted | Notes |
|---|---|---|
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 Ident | refused OE1319 | |
variant payload Type(p) (one positional binder) | ✓ value position | Binds 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: T | refused OE1319 | |
is reasoning-outcome | refused OE1319 | |
arm guard pat if cond | refused 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 position — let 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.