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?