← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Rust Pattern Matching

rust Intermediate
debt(d6/e3/b3/t7)
d6 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5), but bumped to d6: clippy can flag the `_ => ` code_pattern on owned enums, yet the silent-swallow danger only surfaces when a future variant is added and the catch-all quietly absorbs it, so detection sits between specialist tool and careful review.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3): the quick_fix is to replace a `_ =>` catch-all with explicit arms for each variant, a localised pattern swap within one match expression rather than a single-line change or cross-file refactor.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3): although match applies to all Rust contexts (web/cli/queue/library), the consequence of a catch-all is confined to the component owning that enum and match; it does not impose system-wide gravitational pull.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7): the misconception that match is just a switch with a harmless default contradicts Rust's exhaustiveness guarantee, plus bare lowercase names bind new variables instead of comparing constants and arm ordering shadows specific patterns — these contradict how switch/case behaves in other languages.

About DEBT scoring →

Also Known As

rust match expression if let match arms destructuring rust

TL;DR

Rust's match and if let destructure values and enforce exhaustive handling of every variant at compile time.

Explanation

Pattern matching is one of Rust's defining features. The `match` expression compares a value against a series of patterns and runs the arm whose pattern matches first. Unlike a C-style switch, `match` is an expression that yields a value, and it is exhaustive: the compiler refuses to compile a `match` that fails to cover every possible case. This exhaustiveness check is the heart of why pattern matching matters, because it turns 'I forgot to handle that variant' from a runtime surprise into a compile error.

Patterns can destructure almost any value: enum variants like `Some(x)` or `Ordering::Less`, tuples `(a, b)`, structs `Point { x, y }`, slices `[first, rest @ ..]`, ranges `1..=5`, and literals. Bindings introduced in a pattern, such as the `x` in `Some(x)`, are available in that arm's body. Guards add a boolean condition with `if`, and the binding operator `@` lets you capture a value while also testing it against a sub-pattern. The wildcard `_` matches anything without binding, and a bare name like `other` matches anything and binds it, which is the idiomatic catch-all.

For the common case of caring about only one variant, `if let` and `while let` are concise sugar over `match`. `let else` (stable since 1.65) binds a pattern or diverges - returning, breaking, or panicking - in the `else` block, which keeps the happy path unindented. The newer `matches!` macro returns a boolean for quick membership tests.

The most important discipline is avoiding a needless `_ => {}` catch-all on your own enums. A wildcard silences the exhaustiveness check, so when you later add a variant the compiler stays silent instead of pointing you at every place that must be updated. Spelling out each variant, or grouping the genuinely identical ones, preserves that guarantee. Pattern matching combined with enums and `Option`/`Result` is how Rust models state precisely and lets the compiler prove you handled it all.

Common Misconception

match is just a switch statement and a default arm is harmless. In reality match is an exhaustive expression, and adding a wildcard catch-all on your own enums disables the compiler check that would otherwise flag unhandled variants when the enum grows.

Why It Matters

Exhaustive matching turns missed cases into compile errors, but a stray `_ =>` catch-all silently swallows future variants, so a new enum member ships with untested, silently ignored behavior.

Common Mistakes

  • Adding a `_ => {}` catch-all to your own enum so the compiler stops warning when you add a new variant later.
  • Using a deep nested match to test a single variant when `if let` or `matches!` would be far clearer.
  • Forgetting that a bare lowercase name in a pattern binds a new variable rather than comparing against a constant.
  • Writing arms in the wrong order so a broad pattern shadows a more specific one that can never match.
  • Cloning or moving out of a matched value unnecessarily instead of matching on a reference with `ref` or `&`.

Avoid When

  • Matching on a foreign or non-exhaustive enum marked #[non_exhaustive], where a wildcard arm is required by the compiler.
  • Matching on an open-ended type like an integer or string where enumerating every value is impossible and a catch-all is the only option.
  • Quick boolean checks where the `matches!` macro reads more clearly than a full match expression.

When To Use

  • Handling every variant of an enum you own so the compiler forces updates when the enum grows.
  • Destructuring Option, Result, tuples, or structs to bind inner values cleanly in one place.
  • Branching on the shape of data with guards and ranges instead of chained if/else comparisons.
  • Extracting a single variant concisely with if let or let else to keep the happy path unindented.

Code Examples

✗ Vulnerable
enum Event {
    Click { x: i32, y: i32 },
    KeyPress(char),
    Scroll(i32),
}

fn handle(event: &Event) -> String {
    match event {
        Event::Click { x, y } => format!("click at {},{}", x, y),
        // The catch-all silences the exhaustiveness check.
        // When a new variant is added, this arm swallows it silently.
        _ => "unhandled".to_string(),
    }
}

fn main() {
    let e = Event::KeyPress('a');
    // KeyPress and Scroll both fall into the catch-all unnoticed.
    println!("{}", handle(&e));
}
✓ Fixed
enum Event {
    Click { x: i32, y: i32 },
    KeyPress(char),
    Scroll(i32),
}

fn handle(event: &Event) -> String {
    // Every variant is spelled out, so adding one is a compile error
    // until this match is updated.
    match event {
        Event::Click { x, y } => format!("click at {},{}", x, y),
        Event::KeyPress(c) => format!("key {}", c),
        Event::Scroll(delta) => format!("scroll {}", delta),
    }
}

fn main() {
    let e = Event::KeyPress('a');
    println!("{}", handle(&e));

    // if let for the single-variant case keeps the happy path flat.
    if let Event::Scroll(delta) = &e {
        println!("scrolled {}", delta);
    }
}

Added 5 Jun 2026
Views 8
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 2 pings S 1 ping M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Google 2 Scrapy 1 Amazonbot 1
crawler 4
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Low
⚡ Quick Fix
Replace a `_ =>` catch-all on your own enums with explicit arms for each variant so the compiler flags future additions.
📦 Applies To
web cli queue-worker library
🔗 Prerequisites
🔍 Detection Hints
(?<![A-Za-z0-9_])_\s*=>
Auto-detectable: ✓ Yes clippy
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Medium Context: Function Tests: Update

✓ schema.org compliant