🎓 A1Learner
🔍

Rust Match Ergonomics

Match ergonomics is Rust's automatic borrowing in patterns. Every name in the pattern then borrows instead of taking.

The old way: ref on every name

Before match ergonomics, each name needed its own ref to borrow instead of move:

let Player { ref name, ref score } = player;
println!("{name} has {score} points");

A plain pattern on a reference just works

No & in the pattern and no ref on the names. Rust looks behind the reference, and both names borrow — name is a &String and score a &u32:

let Player { name, score } = &player;
println!("{name} has {score} points");

The value stays whole afterwards

The match borrowed the fields, so player keeps its pieces and prints in full after the match:

match &player {
    Player { name, score } => println!("{name} has {score}"),
}
println!("still here: {}", player.name);

Change the real value through &mut

Match &mut instead, and the bindings borrow mutably. The arm writes through the star to the real score:

let mut p2 = Player { name: String::from("Crab"), score: 10 };
match &mut p2 {
    Player { score, .. } => *score += 5,
}
println!("new score = {}", p2.score);

@ keeps hold while it tests

An @ binding behind a reference binds a reference. The range tests the value. The name lends it to you. So the Option is only borrowed, never moved:

let reading = Some(7);
match &reading {
    Some(n @ 5..=10) => println!("n is {}", n),
    _ => println!("other"),
}
println!("still owned: {:?}", reading);

Two fields in one pattern

Inside a method, calling two getter methods collides — each one borrows all of self. One pattern borrows the fields separately instead, because the pattern names the pieces:

let Pair { a, b } = self; // self is &mut Pair, so a and b are &mut fields
a.push(b.len() as i32);

Example

struct Player { name: String, score: u32 }
fn main() {
    let player = Player { name: String::from("Ferris"), score: 30 };
    let report = match &player {
        Player { name, score } if *score >= 50 => format!("{} is winning", name),
        Player { name, score } => format!("{} has {} points", name, score),
    };
    println!("{}", report);
    println!("player still here: {}", player.name);
    let mut p2 = Player { name: String::from("Crab"), score: 10 };
    match &mut p2 {
        Player { score, .. } => *score += 5,
    }
    println!("new score = {}", p2.score);
}

Key points

  • A non-Copy value still cannot move out of a reference this way.
  • Borrowing is the only thing a reference can offer.

In this example, player is a Player with a name and a score of 30. The match runs on &player, a reference, yet the patterns are written plainly, with no & in front. Rust looks behind the reference, so name binds as a &String and score as a &u32. The guard tests *score, following the reference to the number. 30 is not >= 50. So the second arm builds the report "Ferris has 30 points". player itself was never touched, and the next line prints its name in full. Below, p2 is matched through &mut. The arm changes the real score through *score += 5. The last line shows new score = 15.

Note

Match ergonomics is also called default binding modes.

Older code spells the same thing by hand, with & patterns and ref.

Both spellings mean the same — read them as one idea.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

You match on &player with the pattern Player { name, score }. What do name and score become?