What is a partial move?
Short answer: Moving one field out of a struct moves exactly that field. The others stay usable, but the struct as a whole is finished: with a field missing it can’t be moved or borrowed, so the compiler refuses every use of the whole value and leaves the remaining fields alone.
Moving one field
Section titled “Moving one field”let name = person.name; takes the name field and nothing else. The rest of the struct stays in
place, and the fields that weren’t moved keep working.
Every block on this page is a complete src/main.rs. Start from any Cargo project (cargo new partial-move will do — the output doesn’t depend on the name) and replace the file one block at a
time:
struct Person { name: String, nickname: String,}
fn main() { let person = Person { name: String::from("Ada"), nickname: String::from("Lady Lovelace"), };
let name = person.name;
println!("name: {name}"); println!("nickname: {}", person.nickname);}name: Adanickname: Lady LovelaceOnly the name field moved. nickname is a String too, and it is still there to read: the
compiler tracks the fields separately, so one of them leaving says nothing about the other. A Copy
field is different — reading person.age where age is a u32 copies the number out, so it never
starts a partial move at all.
The whole struct is off-limits
Section titled “The whole struct is off-limits”After the partial move the struct can’t be used as one value any more. Try to move it and the compiler names the state exactly:
struct Person { name: String, nickname: String,}
fn main() { let person = Person { name: String::from("Ada"), nickname: String::from("Lady Lovelace"), };
let name = person.name; let whole = person;
println!("{name} is {}", whole.nickname);}error[E0382]: use of partially moved value: `person` --> src/main.rs:13:17 |12 | let name = person.name; | ----------- value partially moved here13 | let whole = person; | ^^^^^^ value used here after partial move | = note: partial move occurs because `person.name` has type `String`, which does not implement the `Copy` trait
For more information about this error, try `rustc --explain E0382`.use of partially moved value is the whole diagnosis: the move that caused it is marked value partially moved here, and the note names the field and says why it moved. The code is E0382, the
familiar moved-value error with partially inserted.
Borrowing the whole struct is refused too. Here is a debug print after the field is gone:
#[derive(Debug)]struct Person { name: String, nickname: String,}
fn main() { let person = Person { name: String::from("Ada"), nickname: String::from("Lady Lovelace"), };
let name = person.name;
println!("{name}"); println!("{person:?}");}error[E0382]: borrow of partially moved value: `person` --> src/main.rs:16:16 |13 | let name = person.name; | ----------- value partially moved here...16 | println!("{person:?}"); | ^^^^^^ value borrowed here after partial move | = note: partial move occurs because `person.name` has type `String`, which does not implement the `Copy` trait
For more information about this error, try `rustc --explain E0382`.What still works is per-field access: person.nickname compiled fine in the first example. The
refusal is on the whole value, not on the struct’s type or its remaining fields.
Why the whole value is refused
Section titled “Why the whole value is refused”The compiler doesn’t track “the struct was moved”. It tracks each field’s initialisation separately — the bookkeeping described for whole values, one field down. A struct with a missing field can’t be dropped as a unit: the drop would run a destructor for a field that no longer lives there. So the compiler refuses every use of the whole struct and drops the remaining fields normally at the end of the scope. Give the fields destructors that print and the split becomes visible:
struct Noisy(&'static str);
impl Drop for Noisy { fn drop(&mut self) { println!("dropping {}", self.0); }}
struct Person { name: Noisy, nickname: Noisy,}
fn main() { let person = Person { name: Noisy("name"), nickname: Noisy("nickname"), };
let name = person.name;
println!("took the name field");}took the name fielddropping namedropping nicknamename drops first: locals drop in reverse declaration order, and it was declared after person, so
it holds the field that moved. Then person drops and its remaining field goes with it. Each
destructor runs exactly once.
A struct that implements Drop can’t be partially moved at all
Section titled “A struct that implements Drop can’t be partially moved at all”If the struct itself has a destructor, none of this is on the table. Drop::drop takes &mut self,
so it could read any field — and one of them would already be gone:
struct Person { name: String,}
impl Drop for Person { fn drop(&mut self) { println!("dropping a person"); }}
fn main() { let person = Person { name: String::from("Ada"), };
let name = person.name; println!("{name}");}error[E0509]: cannot move out of type `Person`, which implements the `Drop` trait --> src/main.rs:16:16 |16 | let name = person.name; | ^^^^^^^^^^^ | | | cannot move out of here | move occurs because `person.name` has type `String`, which does not implement the `Copy` trait |help: consider borrowing here |16 | let name = &person.name; | +help: consider cloning the value if the performance cost is acceptable |16 | let name = person.name.clone(); | ++++++++
For more information about this error, try `rustc --explain E0509`.The move itself can’t be made legal: borrow the field (&person.name) or clone it, or keep the
value you need out of a Drop type. Moving a field out of a struct you only hold by reference
(&Person) is refused for the same reason: a borrower owns nothing to give away. There too the
choices are &person.name or person.name.clone(). The Borrowing book covers the
details.
Related
Section titled “Related”- What happens to the original variable after a move? — the same tracking, applied to whole values
- How to loop over a Vec without moving it — the previous step in this chapter
- How to fix error E0382: borrow of moved value — every fix for the errors above
- Moves and ownership — the rest of this chapter
- Next in this chapter: how ownership works with structs