Skip to content
La3 Docs
Browse docs

Patterns and Matching

Exhaustive matching, destructuring, ranges, guards, bindings, and pattern-driven control flow.

Exhaustiveness

match should make every possible shape visible unless _ intentionally catches the remainder.

An exhaustive match is both a correctness rule and a maintenance tool. When a new enum variant is added, every incomplete match becomes a useful compile-time pointer to code that needs a decision.

  • Use explicit variants when each case matters.
  • Use _ only when the remaining cases truly share behaviour.
  • Prefer a final _ arm over scattered defensive defaults when the reader should see that the rest is intentionally grouped.

Destructuring

Patterns can bind the useful parts of structs, tuples, and enum variants directly.

  • Tuple-like variants bind by position.
  • Struct-like variants bind by field name.
  • Tuple patterns make multi-return values readable at the call site.
  • Destructuring should reveal intent, not become a puzzle of nested punctuation.
match shape {
    Shape.Circle(r) => math.pi * r ** 2,
    Shape.Rect { width, height } => width * height,
}

Ranges and guards

Ranges and guards let a match express classification logic without a separate ladder of conditionals.

  • Ranges are useful for byte classes, status-code classes, and protocol fields.
  • Guards run after the pattern shape matches.
  • If a guard fails, matching continues to the next arm.
  • The most specific arms should come first because matches are tested top to bottom.
match byte {
    0x00 => "null",
    0x01..=0x1f => "control",
    0x20..=0x7e => "printable",
    _ => "extended",
}

Single-pattern control

if let and while let keep one-shape control flow from becoming ceremonial.

Use the short forms when the non-matching case is empty or obvious. Use a full match when the other cases carry information the reader should see.

if let Some(port) = config.port {
    bind(port)
}

while let Some(item) = queue.pop() {
    process(item)
}