Enums and Pattern Matching

Matching References

When the scrutinee of a match, if let, or let expression is a reference (&T or &mut T), Rust's match ergonomics (default binding modes) automatically propagate the reference down to pattern bindings so you can write clean constructor patterns (Some(val)) without manually writing & and ref at every nesting level.

How Match Ergonomics Works

When matching a reference (&T or &mut T) against a non-reference constructor pattern (such as Some(x) or Point { x, y }):

  1. The compiler automatically dereferences the scrutinee to test the constructor.
  2. The default binding mode shifts from by-value (move) to by-reference (ref for &T, or ref mut for &mut T).
  3. As a result, x is bound as &Inner (or &mut Inner) rather than moving Inner out of the reference!
Scrutinee Type Pattern Written Resulting Type of val
opt: Option<String> (by value) Some(val) String (moved by value)
opt: &Option<String> (shared ref) Some(val) &String (borrowed immutably via match ergonomics)
opt: &mut Option<String> (mutable ref) Some(val) &mut String (borrowed mutably via match ergonomics)
opt: &Option<i32> (i32: Copy) Some(&val) i32 (&val explicitly matches the reference and copies i32)
fn main() {
    let mut name: Option<String> = Some(String::from("Ada"));

    // 1. Matching on `&name` binds `s` as `&String` (no move occurs):
    if let Some(s) = &name {
        println!("Read via &name: {s}");
    }

    // 2. Matching on `&mut name` binds `s` as `&mut String`:
    if let Some(s) = &mut name {
        s.push_str(" Lovelace");
    }

    println!("After mutation: {name:?}");
}
Read via &name: Ada
After mutation: Some("Ada Lovelace")

Stripping & in Patterns on Copy Types (&x)

When iterating over or matching a reference to a Copy type (such as opt: &Option<i32> or slice.iter() yielding &i32), matching with &x in the pattern peels off the reference and binds x as a plain i32 value instead of &i32:

fn sum_even(numbers: &[i32]) -> i32 {
    let mut total = 0;
    // `numbers.iter()` yields `&i32`; pattern `&n` destructures the reference so `n: i32`
    for &n in numbers {
        if n % 2 == 0 {
            total += n;
        }
    }
    total
}

fn main() {
    println!("Sum of evens: {}", sum_even(&[1, 2, 3, 4, 6]));
}
Sum of evens: 12

Explicit ref and ref mut Bindings When Matching by Value

When the scrutinee itself is owned by value (or when one field of a struct should be moved while another field is borrowed in the same pattern), use the ref or ref mut keyword on individual bindings:

struct Job {
    id: u64,
    payload: String,
}

fn main() {
    let mut job = Job {
        id: 7,
        payload: String::from("initial"),
    };

    // Match `job` without `&`, copying `id` by value while borrowing `payload` mutably:
    match job {
        Job {
            id,
            ref mut payload,
        } => {
            payload.push_str(" [processed]");
            println!("Job #{id}: {payload}");
        }
    }

    // `job` is still intact because `payload` was borrowed with `ref mut`, not moved!
    println!("Final payload: {}", job.payload);
}
Job #7: initial [processed]
Final payload: initial [processed]

Rust 2024 Edition Match Ergonomics Refinements

In the Rust 2024 edition, match ergonomics enforces cleaner separation between explicit & / &mut reference patterns and implicit reference binding modes:

  • Once a pattern shifts into an implicit reference binding mode (ref or ref mut), writing &x inside that subpattern to peel a reference and simultaneously bind by reference is disallowed; instead, write x directly (or match on the explicit reference outer structure).
  • mut on a binding inside an implicit reference pattern no longer resets the binding mode back to by-value—it simply makes the local variable binding mut.