Skip to content

Pattern Language

Chris Michael edited this page Sep 2, 2026 · 2 revisions

Pudu

Pattern Language

A pattern asks whether a value has a particular shape and, on success, assigns names to selected parts of that value. The pattern vocabulary is used by if let, let … else, while let, for, and match; each construct decides whether failure is permitted. Ordinary let binds a name and does not provide general destructuring.

Pattern forms

Form Meaning
_ Accept and discard any value.
name Accept and bind the whole value.
42, true, 'x', "ok" Require an equal literal.
(left, right) Decompose a tuple.
{x, y: other} Decompose record fields.
Some(value) Require and decompose a variant.
`left right`

Patterns do not execute arbitrary expressions. A guard belongs to a match arm and runs only after its structural pattern succeeds.

Irrefutable and refutable patterns

An irrefutable pattern must succeed for every value of its checked type. Names, wildcards, and a tuple pattern against a known tuple are typical examples. Refutable-control forms reject them because there is no meaningful failure path.

match entry {
  case (name, score) => record(name, score)
}

A variant or literal pattern may fail. It therefore requires a construct that states the failure path.

if let Some(score) = selected {
  record(score)
}

let Some(required) = selected else { return Err("missing score") }

Using an irrefutable pattern with if let, let … else, or while let is rejected: a condition that can never fail obscures an ordinary binding, a direct match, or an unconditional loop.

Binding scope

Pattern names exist only where success has been established.

  • if let: the success block.
  • while let: the loop body for that iteration.
  • for: the loop body for that element.
  • match: the selected arm, including its guard.
  • let … else: the rest of the containing block after the declaration.

An else block of if let cannot use the success bindings. The subject is evaluated once for if let and match, and before every attempted iteration for while let.

Alternatives

Alternatives are parsed with | and work when they introduce no bindings.

match response {
  case Accepted(_) | Cached(_) => "available"
  case Rejected(reason) => report(reason)
}

The language rule requires both sides of an alternative to introduce the same names with compatible types. The current resolver does not implement that rule correctly: repeating the same binder in both alternatives is reported as a duplicate declaration, while different binders may pass checking and leave the unchosen name undefined at run time. Binder-carrying alternatives are therefore not usable correctly in the current implementation. Alternatives with different names remain invalid by design because the arm body cannot have one stable environment.

Guards and coverage

match value {
  case Some(found) if found > 0 => found
  case Some(_) => 0
  case None => 0
}

Arms are considered from top to bottom. A guard is evaluated only after the pattern succeeds. A guarded arm never proves exhaustiveness because the guard may be false. The unguarded Some(_) arm above is therefore semantically necessary, not redundant.

Exhaustiveness and reachability

The checker proves coverage for closed sums and booleans. A missing case is an error. An arm fully covered by earlier unguarded arms is unreachable and produces a warning. A wildcard is useful at an intentionally open boundary, but explicit variants retain better compiler help when a closed sum evolves.

Related

Clone this wiki locally