Skip to content
Select theme

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:

printing hello
done

text 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:

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` trait
help: 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).

printing hello
still 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.

printing hello
still 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”
the name is Ada
the age still works: 36

A 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).