Enums and Pattern Matching

Common Pattern-Matching Errors

Most pattern-matching mistakes in Rust fall into four categories: accidentally shadowing a variable instead of comparing its value, moving a non-Copy field out of a borrowed reference, ordering general patterns before specific patterns, and simultaneously binding a whole value while moving one of its fields.

Summary of Common Pattern-Matching Pitfalls

Pitfall Compiler Warning or Error Root Cause Fix
Bare identifier in match arm (expected_val => ...) Warning: unreachable_patterns + unused_variables A bare lowercase identifier in a pattern is a new variable binding that matches anything, not an equality check against an outer variable Use a const (EXPECTED_VAL) or a match guard (x if x == expected_val)
Moving a non-Copy field out of &Enum or &Struct E0507: cannot move out of ... which is behind a shared reference Explicit *deref or by-value move of a String/Vec behind & Match on &scrutinee directly so match ergonomics binds &String, or clone
Binding whole @ Variant(inner) when inner moves E0382: use of partially moved value Moving inner out of Variant invalidates whole Match by reference (&scrutinee) or bind ref inner
Placing a general pattern before a specific pattern Warning: unreachable pattern match evaluates arms top-to-bottom Order specific arms before general or catch-all arms

Error 1: Accidental Variable Shadowing in a match Arm

A bare variable name in a pattern always creates a new binding that matches every value and shadows any outer variable of the same name:

fn check_pin(input: u16) {
    let secret_pin = 1234;

    match input {
        // BUG: `secret_pin` here is a brand-new pattern variable that matches ANY `u16`!
        // It does NOT compare `input == 1234`!
        x if x == secret_pin => println!("PIN matched ({x})!"),
        other => println!("Wrong PIN: {other}"),
    }
}

fn main() {
    check_pin(9999);
    check_pin(1234);
}
Wrong PIN: 9999
PIN matched (1234)!

Always use either a const item (const SECRET_PIN: u16 = 1234; can be used directly as a pattern!) or a match guard (x if x == secret_pin) when comparing against a runtime variable.

Error 2: Moving Out of a Shared Reference in a Pattern (E0507)

If you dereference a shared reference in the match scrutinee (match *opt_ref) when the inner type is not Copy, attempting to bind the payload by value fails with E0507:

// ❌ does not compile
fn print_opt(opt: &Option<String>) {
    match *opt {
        Some(text) => println!("{text}"),
        None => println!("empty"),
    }
}

fn main() {}
error[E0507]: cannot move out of `*opt` which is behind a shared reference
 --> src/main.rs:2:11
  |
2 |     match *opt {
  |           ^^^^
3 |         Some(text) => println!("{text}"),
  |              ----
  |              |
  |              data moved here
  |              move occurs because `text` has type `String`, which does not implement the `Copy` trait
  |
help: consider removing the dereference here
  |
2 -     match *opt {
2 +     match opt {
  |

Fix: Match on opt Directly Without *

Matching on opt (&Option<String>) activates match ergonomics and binds text as &String without moving.

Error 3: Simultaneously Binding whole @ and Moving an Inner Field (E0382)

When matching an owned non-Copy value by value, whole @ Wrapper(inner) attempts to move the entire value into whole while also moving its inner field into inner:

// ❌ does not compile
#[derive(Debug)]
struct Wrapper(String);

fn main() {
    let w = Wrapper(String::from("data"));
    match w {
        whole @ Wrapper(inner) => println!("{whole:?}, {inner}"),
    }
}
error[E0382]: borrow of moved value: `w`
 --> src/main.rs:7:9
  |
7 |         whole @ Wrapper(inner) => println!("{whole:?}, {inner}"),
  |         ^^^^^^^^^^^^^^^^-----^
  |         |               |
  |         |               value moved here
  |         value borrowed here after move
  |
  = note: move occurs because `w.0` has type `String`, which does not implement the `Copy` trait
help: borrow this binding in the pattern to avoid moving the value
  |
7 |         ref whole @ Wrapper(ref inner) => println!("{whole:?}, {inner}"),
  |         +++                 +++

Fix: Match &w by Reference (or Use ref)

#[derive(Debug)]
struct Wrapper(String);

fn main() {
    let w = Wrapper(String::from("data"));
    match &w {
        whole @ Wrapper(inner) => println!("{whole:?}, {inner}"),
    }
}
Wrapper("data"), data