Skip to content

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.

PatternExampleMatches
Wildcardcase _:anything
Literalcase 0:, case "fee":that exact scalar/string
Bindcase x:anything, binding it to x
Bind + guardcase x if p(x):anything for which the guard holds
Structcase Money(currency=c, minor_units=m):that struct, binding fields
Enumcase Escalation.page(oncall, deadline):that variant, binding payload
Tuplecase (a, b):a 2-tuple, binding elements
Regexcase re"^FEE (?P<n:int>[0-9]+)$":a string matching the pattern
Or-patterncase Ok(x) | Cached(x):either alternative

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: pass

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)

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)

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.

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)
  • 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 to match.
a, b = pair # ok: irrefutable
Money(currency=c, minor_units=m) = price # ok: irrefutable destructure
# Some(x) = maybe_user # error: refutable — use match

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 string literals are typed templates, not string concatenation.

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 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)

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.

  • Non-exhaustive enum/struct matchsema check error listing the missing cases.
  • Regex/string match without a wildcardassure silver failure.
  • Refutable pattern in assignment position → compile error, directing to match.
  • Or-pattern alternatives binding different names/types → compile error.
  • Raw string used where SqlQuery is required, or user identifier without endorsement → compile error / policy denial.
  • Semantic guard in a case → types the branch statistical(α), needs monitor coverage.