Rust 2024 Edition

Match Ergonomics Changes

Match ergonomics is the feature in Rust (originally introduced in Rust 2018) that allows matching a reference &Option<T> or &mut Option<T> directly against a value pattern Some(x) without writing &Some(ref x). When the pattern matcher steps past a reference & or &mut, it switches its internal default binding mode from by-value (move) to by-reference (ref or ref mut).

In the Rust 2024 edition, match ergonomics were simplified and made more consistent by disallowing explicit mut, ref, or ref mut modifiers inside a pattern after the default binding mode has already switched to by-reference, and by preventing & patterns from simultaneously dereferencing a reference and resetting the binding mode to by-value (mut reset bug).

Matching a Reference Scrutinee `let opt: &Option<i32> = &Some(42);`

  Rust 2021 (Confusing Mixing of Implicit Reference Mode + Explicit `mut`/`ref`):
    `if let Some(mut x) = opt`
    --> Stepping past `&Option` switched mode to `ref`, and then `mut x`
        silently reset the binding mode back to BY-VALUE (`x: i32` copied)!

  Rust 2024 (Consistent & Explicit Pattern Rules):
    - To bind by shared reference (`x: &i32`):    `if let Some(x) = opt`
    - To copy out by value into `mut x: i32`:     `if let &Some(mut x) = opt` (or `let mut x = *x;`)

Why Rust 2021's mut Binding Reset Was Error-Prone

In Rust 2021, when matching opt: &Option<i32>:

  • Writing if let Some(x) = opt bound x: &i32 (by reference, via match ergonomics).
  • However, merely adding mut—if let Some(mut x) = opt—silently reset the binding mode from by-reference back to by-value, binding x: i32 (a mutable local copy) instead of &i32! Worse yet, if the inner type was not Copy (such as &Option<String>), writing if let Some(mut s) = opt suddenly failed with cannot move out of opt!

In Rust 2024, once match ergonomics has inherited a reference (& or &mut from the scrutinee), writing mut, ref, or ref mut on a binding inside that pattern is a compile error:

// ❌ does not compile
fn main() {
    let opt: &Option<i32> = &Some(10);

    // In Rust 2024, `mut x` is rejected when match ergonomics is already in `ref` mode:
    if let Some(mut x) = opt {
        x += 1;
        println!("{x}");
    }
}
error: `mut` on a binding may not be affected by default binding mode
 --> src/main.rs:5:17
  |
5 |     if let Some(mut x) = opt {
  |                 ^^^^^
  |
  = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/match-ergonomics.html>
help: match the reference with `&` or dereference the scrutinee `*opt`
  |
5 |     if let &Some(mut x) = opt {
  |            +

Clean, Unambiguous Patterns in Rust 2024

Scrutinee Type Desired Binding for x Idiomatic Rust 2024 Pattern
opt: &Option<T> x: &T (shared borrow) if let Some(x) = opt
opt: &mut Option<T> x: &mut T (mutable borrow) if let Some(x) = opt (no ref mut needed!)
opt: &Option<i32> (Copy) mut x: i32 (mutable local copy) if let Some(mut x) = *opt (or if let &Some(mut x) = opt)
opt: Option<T> (by value) x: &T (borrow without moving opt) if let Some(x) = &opt (prefer &opt on the right over Some(ref x)!)
fn main() {
    // 1. Borrowing `Option<String>` via `&opt` on the scrutinee (cleanest style!):
    let owned_opt = Some(String::from("rust-2024"));
    if let Some(text_ref) = &owned_opt {
        // `text_ref` is `&String` automatically
        println!("Borrowed via &owned_opt: {text_ref}");
    }

    // 2. Mutating in-place via `&mut owned_opt`:
    let mut mutable_opt = Some(vec![1, 2, 3]);
    if let Some(vec_mut) = &mut mutable_opt {
        // `vec_mut` is `&mut Vec<i32>` automatically
        vec_mut.push(4);
    }
    assert_eq!(mutable_opt, Some(vec![1, 2, 3, 4]));

    // 3. Copying a `Copy` value out of a reference by dereferencing `*ref_opt`:
    let ref_opt: &Option<i32> = &Some(41);
    if let Some(mut local_copy) = *ref_opt {
        local_copy += 1;
        assert_eq!(local_copy, 42);
        println!("Mutated local copy from *ref_opt: {local_copy}");
    }
}
Borrowed via &owned_opt: rust-2024
Mutated local copy from *ref_opt: 42