Skip to content
Select theme

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.

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:

Ada is 36
age is still here: 36

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

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.

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.

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:

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 here
17 |
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 function
note: 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.