How ownership works with match expressions
Short answer: An arm moves when its pattern binds a non-Copy value by value, which is the
default when the matched value is owned. An arm borrows when the pattern binds by reference —
ref or ref mut inside the pattern, or a reference as the matched value, where match
ergonomics turns every binding into a reference. An arm that binds nothing, like _, moves
nothing, and a binding of a Copy type copies rather than moves.
An arm that binds by value takes the value with it
Section titled “An arm that binds by value takes the value with it”Every block on this page is a complete src/main.rs. Start from any Cargo project —
cargo new match-own will do, the output doesn’t depend on the name — and replace the file
one block at a time:
#[derive(Debug)]enum Command { Print(String), Quit,}
fn main() { let command = Command::Print(String::from("hello"));
match command { Command::Print(text) => println!("printing {text}"), Command::Quit => println!("quitting"), }
println!("done");}printing hellodonetext is bound by value, so the arm takes ownership of the String inside the variant.
command gave it away — the same move as passing the payload to a function
(how to pass ownership of a value to a function).
When nothing needs command after the match, this is the normal way to write it.
Use the value after the match and the compiler stops you
Section titled “Use the value after the match and the compiler stops you”Print command after the match — Debug is what the derive in the same block is for — and
the build fails:
#[derive(Debug)]enum Command { Print(String), Quit,}
fn main() { let command = Command::Print(String::from("hello"));
match command { Command::Print(text) => println!("printing {text}"), Command::Quit => println!("quitting"), }
println!("the command was {command:?}");}error[E0382]: borrow of partially moved value: `command` --> src/main.rs:15:32 |11 | Command::Print(text) => println!("printing {text}"), | ---- value partially moved here...15 | println!("the command was {command:?}"); | ^^^^^^^ value borrowed here after partial move | = note: partial move occurs because value has type `String`, which does not implement the `Copy` traithelp: borrow this binding in the pattern to avoid moving the value |11 | Command::Print(ref text) => println!("printing {text}"), | +++
For more information about this error, try `rustc --explain E0382`.The diagnostic is the E0382 from
how to fix error E0382, and it says three
things worth reading. The move happened on the binding — value partially moved here points
at text, not at the match — because the note underneath says why: String does not
implement the Copy trait. And the compiler’s own fix is the rest of this page, written into
the arm as Command::Print(ref text).
Match a reference and every arm borrows
Section titled “Match a reference and every arm borrows”#[derive(Debug)]enum Command { Print(String), Quit,}
fn main() { let command = Command::Print(String::from("hello"));
match &command { Command::Print(text) => { let borrowed: &String = text; println!("printing {borrowed}"); } Command::Quit => println!("quitting"), }
println!("still owned: {command:?}");}printing hellostill owned: Print("hello")The matched value is a reference, so match ergonomics flips the default binding mode and
text is a &String — the let borrowed: &String = text; line is the compiler checking
that, not a cast. Nothing moves, and command is still owned when the match ends.
ref borrows from inside the pattern
Section titled “ref borrows from inside the pattern”#[derive(Debug)]enum Command { Print(String), Quit,}
fn main() { let command = Command::Print(String::from("hello"));
match command { Command::Print(ref text) => { let borrowed: &String = text; println!("printing {borrowed}"); } Command::Quit => println!("quitting"), }
println!("still owned: {command:?}");}printing hellostill owned: Print("hello")ref says “bind this by reference” in the pattern itself, which is the way to borrow while
the matched value stays owned. ref mut borrows mutably; matching &mut command does the
same through the matched value, and an arm holding a &mut String can call push_str on it
without taking the String. if let is a match with one arm, and it follows the same rules.
A struct pattern moves the fields it binds
Section titled “A struct pattern moves the fields it binds”struct Person { name: String, age: u32,}
fn main() { let person = Person { name: String::from("Ada"), age: 36, };
match person { Person { name, .. } => println!("the name is {name}"), }
println!("the age still works: {}", person.age);}the name is Adathe age still works: 36A struct pattern binds by value too, so name moves out of person while everything the
pattern ignores stays put: person.age still works, and using person as one value
afterwards would be the partial move from
what is a partial move?. An arm that binds
nothing — _ or a literal — moves nothing, and matching a Copy type like i32
copies the binding, so the value stays usable afterwards
(how to tell whether a type will move or copy).
Related
Section titled “Related”- What happens when you assign one struct to another variable? — the previous step: whole-struct moves, and when they copy
- What is a partial move? — one field leaves, and the whole struct is off-limits
- When should you clone? — when the arm needs its own copy
- How to fix error E0382: borrow of moved value — every fix for the error above
- Moves and ownership — the rest of this chapter
- Next in this chapter: how to fix ownership errors with a Vec