Pattern Matching & Interpolated Literals
match/case is Sema’s structural branching construct. It destructures values,
binds their parts, checks guards, and — where the domain is finite — verifies that
you handled every case. Case order is explicit and there is no fallthrough.
This page also covers interpolated literals (f-strings, regex literals, and
typed SQL templates), because they interlock with matching.
The pattern forms
Section titled “The pattern forms”| Pattern | Example | Matches |
|---|---|---|
| Wildcard | case _: | anything |
| Literal | case 0:, case "fee": | that exact scalar/string |
| Bind | case x: | anything, binding it to x |
| Bind + guard | case x if p(x): | anything for which the guard holds |
| Struct | case Money(currency=c, minor_units=m): | that struct, binding fields |
| Enum | case Escalation.page(oncall, deadline): | that variant, binding payload |
| Tuple | case (a, b): | a 2-tuple, binding elements |
| Regex | case re"^FEE (?P<n:int>[0-9]+)$": | a string matching the pattern |
| Or-pattern | case Ok(x) | Cached(x): | either alternative |
Matching enums and their payloads
Section titled “Matching enums and their payloads”Enum variant payloads destructure positionally or by name. Enum match is
exhaustiveness-checked — if you miss a variant, sema check tells you:
enum Escalation: none notify(channel: str) page(oncall: str, deadline: Duration)
match esc: case Escalation.page(oncall, deadline): dispatch(oncall, deadline) case Escalation.notify(channel): post(channel) case Escalation.none: passMatching Option and Result
Section titled “Matching Option and Result”Because there is no null, you consume Option/Result by matching — it is the
exhaustive way to handle presence and failure:
match choose_assignment(incident, resources): case Some(assignment): commit(assignment) case None: defer(incident)
match load_rows(path): case Ok(rows): reconcile(rows) case Err(e): quarantine(path, evidence=e)Structs and tuples
Section titled “Structs and tuples”Struct patterns bind fields by name; a field subset is legal, and positional struct patterns are forbidden (fields are named, not ordered). Tuple patterns destructure positionally:
match price: case Money(currency=c, minor_units=m) if m > 0: settle(c, m) case Money(minor_units=0): skip() # field subset
match pair: case (0, y): on_y_axis(y) case (x, 0): on_x_axis(x) case (x, y): interior(x, y)Guards
Section titled “Guards”A case … if <guard>: arm only matches when the guard also holds. Guards can be
ordinary expressions — or a semantic predicate, which types the branch as
statistical(α) and requires monitor coverage like any semantic decision site:
match memo: case text if semantics("memo describes a chargeback", text, alpha=0.02): return ChargebackMemo(raw=text) case _: return UnknownMemo(raw=memo)See /neurosymbolic/verification/ for how
semantics(...) guards are calibrated and monitored.
Or-patterns
Section titled “Or-patterns”P1 | P2 matches either alternative. Both alternatives must bind the same names
at the same types, and exhaustiveness accounts for the union:
match result: case Ok(v) | Cached(v): use(v) # both bind `v: T` case Err(e): report(e)Refutable vs irrefutable patterns
Section titled “Refutable vs irrefutable patterns”- An irrefutable pattern always matches — it is used for destructuring
assignment:
a, b = pair,Money(currency=c, minor_units=m) = price. - A refutable pattern may fail to match — it belongs in a
match. A refutable pattern in assignment position is a compile error directing you tomatch.
a, b = pair # ok: irrefutableMoney(currency=c, minor_units=m) = price # ok: irrefutable destructure# Some(x) = maybe_user # error: refutable — use matchExhaustiveness
Section titled “Exhaustiveness”Enum and struct patterns are exhaustiveness-checked where the domain is finite.
Regex and string cases are not exhaustiveness-checkable, so they require a
wildcard case _: at assure silver and above. There is no fallthrough — each
case is independent and ordered top to bottom.
Interpolated literals
Section titled “Interpolated literals”Interpolated string literals are typed templates, not string concatenation.
f-strings
Section titled “f-strings”f"…" returns str with segment provenance. The result’s trust label is the meet
of all interpolated values and the literal text — so tainted data stays tainted
through interpolation:
summary = f"latest={latest.major}.{latest.minor} top={top.name}"bullet = f"- {self.item}"validate <expr>: — a contract boundary on a composed value
Section titled “validate <expr>: — a contract boundary on a composed value”validate <expr>: introduces a local contract boundary where value names the
constructed candidate. This is the one-line validator hook for composed strings and
templates:
notice = validate f"Case {case_id}: {summary}": sem "Analyst-facing case notice" ensure len(value) <= 240 check semantics("notice contains no raw account numbers or secrets", value, alpha=0.01)Regex literals and typed captures
Section titled “Regex literals and typed captures”Regex literals use re"…" and are compiled at build time. Named captures bind
locals in match cases, and a capture may declare a deterministic parser with
(?P<name:Type>…) — the compiler lowers this to a regex capture plus a typed
boundary parse, so a failed parse makes the case not match (it is a non-match,
never an exception):
match memo: case re"^ACH CREDIT (?P<counterparty>[A-Z0-9 .-]+) REF (?P<ref>[A-Z0-9-]+)$": return PaymentMemo(counterparty=counterparty, reference=ref) case re"^FEE (?P<minor_units:int>[0-9]+) (?P<currency>[A-Z]{3})$": return FeeMemo(amount=Money(currency=parse_currency(currency), minor_units=minor_units)) case _: return UnknownMemo(raw=memo)Typed SQL templates
Section titled “Typed SQL templates”sql"…" returns a typed SqlQuery, not str. Interpolation holes are bound
parameters by default (never string-spliced), so injection is structurally
impossible. Identifier and fragment interpolation are separate, explicit
capabilities (sql.ident(trusted_name), sql.fragment(validated_fragment)), and a
raw string cannot be executed as SQL:
query = validate sql""" select id, amount_minor, currency, memo from ledger_entries where tenant_id = {tenant_id} and booked_epoch_s >= {start_epoch_s} order by booked_epoch_s desc""": sem "Read-only tenant-scoped ledger lookup" ensure sql.read_only(value) ensure sql.has_parameter(value, "tenant_id") check semantics("query cannot read outside the requested tenant", value, alpha=0.01)Query execution adds db.read, db.write, or db.schema to the caller’s effect
row and policy envelope — the dialect, schema, row type, and effect are inferred
from the connection or declared explicitly.
Failure modes
Section titled “Failure modes”- Non-exhaustive enum/struct match →
sema checkerror listing the missing cases. - Regex/string match without a wildcard →
assure silverfailure. - Refutable pattern in assignment position → compile error, directing to
match. - Or-pattern alternatives binding different names/types → compile error.
- Raw string used where
SqlQueryis required, or user identifier without endorsement → compile error / policy denial. - Semantic guard in a case → types the branch
statistical(α), needs monitor coverage.
See also
Section titled “See also”- Control Flow —
if/for/while/with/loop … until. - Traits, Enums & Generics — declaring the enums you match.
- Error Handling —
expect/except,?, and combinators. - Verification — calibrating
semantics(...)guards.