10.8Ownership in structs
Structs don't escape chapters 8 and 9; they inherit them. A struct owns its fields, and that single fact decides how structs move, what happens when one is dropped, and why a tempting shortcut (storing a reference inside a struct) is the one thing this chapter won't let you do yet.
A struct owns its fields
When a struct holds a String, the struct owns that String. The heap text belongs to the field, the field belongs to the struct, and the struct belongs to whoever owns the struct. Ownership nests, all the way down:
#[derive(Debug)]
struct User {
name: String,
age: u32,
}
fn main() {
let user = User {
name: String::from("Ada"),
age: 36,
};
println!("{:?}", user);
}User { name: "Ada", age: 36 }
user owns the String "Ada". There's no separate owner for the text; it's part of the value. This is why lesson 10.1 chose String fields rather than &str fields for Customer: an owning struct holds its own data and answers to nobody about how long that data lives.
When user goes out of scope at the end of main, it's dropped, and dropping it drops every field in turn: the String's heap block is freed, the u32 reclaims its bytes. The lesson 8.3 drop rule reaches into the struct automatically. You never free the fields yourself; owning the struct means owning the cleanup.
Moving a struct moves its fields
Because the struct owns its fields, moving the struct moves the whole bundle, and the lesson 8.4 move rule applies to the struct as a unit:
fn main() {
let user = User { name: String::from("Ada"), age: 36 };
let user2 = user; // user moved into user2
println!("{:?}", user); // refused
}error[E0382]: borrow of moved value: `user`
--> src/main.rs:4:22
|
2 | let user = User { name: String::from("Ada"), age: 36 };
| ---- move occurs because `user` has type `User`, which does not implement the `Copy` trait
3 | let user2 = user;
| ---- value moved here
4 | println!("{:?}", user);
| ^^^^ value borrowed here after move
Same E0382, same three labels lesson 8.4 taught you to read. The note says it plainly: User "does not implement the Copy trait," so let user2 = user; moves rather than copies. A struct is Copy only if every field is Copy (lesson 8.5), and the moment a struct contains a String it can't be, because the String can't be. A Point { x: f64, y: f64 } could be Copy (both fields are), and you'd opt in with #[derive(Copy, Clone)]; a struct with a String field never can.
The cures are the ones you already know. Borrow it (let user2 = &user;) if you only need to look. Clone it (#[derive(Clone)], then user.clone()) if you genuinely need two independent owners and accept the copy cost (lesson 8.7). Pass &User to functions that read, &mut User to functions that change it, owned User to functions that keep it: the lesson 9.4 table, unchanged, applied to your type.
Key insight
A struct doesn't get special ownership rules; it gets the same rules, applied to the whole at once. Move the struct, you move every field. Drop the struct, you drop every field. Ask whether the struct is Copy, and the answer is "only if all its fields are." Everything you learned about a lone String is true of a struct containing one, because the struct is just a labeled box around its fields.
Why fields are owned, not borrowed
You might wonder why the User struct stores name: String (owned) instead of name: &str (borrowed). A borrowed field would avoid the copy. The reason is the one lesson 9.5 drilled: a reference must never outlive the value it points at. A struct that stored a &str would be storing a borrow, and that borrow would have to stay valid for as long as the struct lives. The compiler can't just take your word for that; it needs you to prove the borrowed-from data outlives the struct, and the tool for writing that proof is called a lifetime.
Try it and the compiler tells you exactly what's missing:
struct Excerpt {
text: &str,
}error[E0106]: missing lifetime specifier
--> src/main.rs:2:11
|
2 | text: &str,
| ^ expected named lifetime parameter
|
help: consider introducing a named lifetime parameter
|
1 ~ struct Excerpt<'a> {
2 ~ text: &'a str,
|
You've seen E0106 before, in lesson 9.5, when a function tried to return a reference to a local. It's the same machinery: the compiler wants you to name a lifetime ('a) so it can check that no Excerpt outlives the text it points into. That <'a> syntax is chapter 17's entire subject, and it's genuinely the next real layer of the type system, so we defer it deliberately rather than rush it here.
Best practice
Until chapter 17, give your structs owned fields: String, not &str; Vec<T>, not &[T]. An owning struct is self-contained, it can move and live anywhere, and it never drags a lifetime annotation into your code. Storing references in structs is a real and useful technique, but it's an intermediate one, and reaching for it before you've met lifetimes leads straight into E0106. Own your data; borrow at the function boundary.
This is the same advice lesson 5.4 gave for strings ("store String, pass &str"), now generalized to every type you'll define. A struct holds owned data; functions borrow it through &self and &T parameters. That division keeps lifetimes out of your structs and your structs free to go anywhere.
Quiz time
Question #1
Does this compile? If not, name the error and give two different fixes.
#[derive(Debug)]
struct Note {
body: String,
}
fn main() {
let a = Note { body: String::from("hi") };
let b = a;
println!("{:?} {:?}", a, b);
}Show solution
It doesn't compile: E0382, use after move. let b = a; moves a (a Note containing a String can't be Copy), so reading a afterward is refused. Two fixes: (1) borrow, let b = &a;, since you only print; or (2) #[derive(Clone)] on Note and let b = a.clone(); for two independent owners. Cloning copies the heap text; borrowing copies nothing.
Question #2
Why can a Point { x: f64, y: f64 } be made Copy, but a User { name: String, age: u32 } can never be?
Show solution
A struct can be Copy only if every field is Copy. f64 is Copy, so all of Point's fields qualify and you can #[derive(Copy, Clone)]. User contains a String, which owns heap memory and is not Copy (lesson 8.5); one non-Copy field disqualifies the whole struct.
Question #3
What does E0106 ("missing lifetime specifier") on a struct field tell you, and what's the simplest way to avoid it for now?
Show solution
It means you tried to store a reference (&str, &T) in a struct, and the compiler needs a lifetime annotation to guarantee the struct never outlives the borrowed data. The simplest fix until chapter 17 is to store an owned field instead: String rather than &str. Owned fields need no lifetimes.
That's everything a struct does on its own. The next lesson puts it all together in the chapter's project: the Rust Book's rectangle refactor, rebuilt one compiling step at a time, from loose variables to a tuple to a struct with methods.