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