How ownership works with structs
Short answer: the compiler tracks a struct’s fields one at a time, not the struct as a blob. Building
a struct moves each field in (a Copy field copies instead of moving), taking one field out moves only
that field, and using the struct as a whole value moves every field at once.
Building a struct moves the fields in
Section titled “Building a struct moves the fields in”A struct literal is a set of moves, one per field. Every block on this page is a complete
src/main.rs. Start from any Cargo project (cargo new struct-ownership will do — the output doesn’t
depend on the name) and replace the file one block at a time:
struct Person { name: String, age: u32,}
fn main() { let name = String::from("Ada"); let age = 36;
let person = Person { name: name, age: age };
println!("{} is {}", person.name, person.age); println!("age is still here: {age}");}Ada is 36age is still here: 36The two fields were treated differently. name is a String, so it moved into the struct and the
local binding died with the move — print it after the literal and the compiler names both the move
and the use:
struct Person { name: String, age: u32,}
fn main() { let name = String::from("Ada"); let age = 36;
let person = Person { name: name, age: age };
println!("{} is {}", person.name, person.age); println!("the name local is gone: {name}");}error[E0382]: borrow of moved value: `name` --> src/main.rs:13:40 | 7 | let name = String::from("Ada"); | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait...10 | let person = Person { name: name, age: age }; | ---- value moved here...13 | println!("the name local is gone: {name}"); | ^^^^ value borrowed here after move |help: consider cloning the value if the performance cost is acceptable |10 | let person = Person { name: name.clone(), age: age }; | ++++++++
For more information about this error, try `rustc --explain E0382`.age is a u32, so it was copied: the literal read the number and the local kept it. That is the
Copy rule from how to tell whether a type will move or copy,
applied field by field. Nothing about the struct changes it: the struct is new, and it takes whatever
each field expression hands it.
Taking a field back out
Section titled “Taking a field back out”The reverse runs the same way: moving a field out of a struct moves that field and leaves the others
alone. What is a partial move? covers it in full — the
short version is that after let n = person.name; the remaining fields still work, while the struct
as a whole is finished. Reading a field without the move — &person.name — belongs to the
Borrowing book.
The whole struct moves as one
Section titled “The whole struct moves as one”Field independence only covers field-level operations. Use the struct as a single value and every
field goes with it, Copy fields included. Passing it to a function that takes Person by value is
the same move described in how to pass ownership of a value to a function:
struct Person { name: String, age: u32,}
fn greet(person: Person) { println!("hello, {}", person.name);}
fn main() { let person = Person { name: String::from("Ada"), age: 36, };
greet(person);
println!("age: {}", person.age);}error[E0382]: borrow of moved value: `person` --> src/main.rs:18:25 |11 | let person = Person { | ------ move occurs because `person` has type `Person`, which does not implement the `Copy` trait...16 | greet(person); | ------ value moved here17 |18 | println!("age: {}", person.age); | ^^^^^^^^^^ value borrowed here after move |note: consider changing this parameter type in function `greet` to borrow instead if owning the value isn't necessary --> src/main.rs:6:18 | 6 | fn greet(person: Person) { | ----- ^^^^^^ this parameter takes ownership of the value | | | in this functionnote: if `Person` implemented `Clone`, you could clone the value --> src/main.rs:1:1 | 1 | struct Person { | ^^^^^^^^^^^^^ consider implementing `Clone` for this type...16 | greet(person); | ------ you could clone this value
For more information about this error, try `rustc --explain E0382`.age is a u32 and it went too: the move is of the whole struct, so the whole binding is
invalidated, not field by field. Assigning one struct to another variable moves it the same way —
that is the next step in this chapter.
Related
Section titled “Related”- What is a partial move? — taking one field out, and what that does to the rest
- How to pass ownership of a value to a function —
whole-value moves with
Stringandi32 - How to tell whether a type will move or copy —
the
Copytest, and why your own structs never pass it by default - Moves and ownership — the rest of this chapter
- Next in this chapter: what happens when you assign one struct to another variable