Lesson 11 of 94
Ownership: The Three Rules
Ownership is the idea that makes Rust different from every language you have used. It is how Rust frees memory without a garbage collector and without manual free() calls, and it is checked entirely at compile time, so it costs nothing at runtime.
This section of the course is long and example-heavy on purpose. If you understand these seven lessons, the rest of Rust follows.
Background: the stack and the heap
A program stores data in two places:
- The stack holds local variables of fixed size. It is very fast, and each function's space is freed automatically when the function returns.
- The heap holds data whose size can change or is not known at compile time, such as the text of a
Stringor the elements of aVec. Something has to decide when heap memory can be reused.
Rust has no garbage collector. Instead, the compiler tracks exactly one owner for every heap value and inserts the code to free the value at the point where its owner goes away.
The three rules
- Each value in Rust has an owner, a variable.
- There can only be one owner at a time.
- When the owner goes out of scope, the value is dropped: its memory is freed immediately.
The examples below show each rule, then what happens when you break it.
1. A variable is valid from its declaration to the end of its scope
fn main() {
// `greeting` does not exist yet
{
let greeting = String::from("hello"); // `greeting` is valid from here
println!("{greeting}");
} // the scope ends: `greeting` is dropped and its heap memory is freed
println!("greeting is gone");
}hello
greeting is goneThis is rule 3. The freeing happens at the closing brace, at a moment you can see in the source code, not "at some point later" as with a garbage collector.
2. What a String looks like in memory
A String has two parts. On the stack, a small fixed-size header with three numbers: a pointer to the text, the length, and the capacity. On the heap, the text itself.
stack (String) heap
┌──────────┬───────┐ ┌───┬───┬───┬───┬───┐
│ ptr │ ─────┼──────────► │ h │ e │ l │ l │ o │
│ len │ 5 │ └───┴───┴───┴───┴───┘
│ capacity │ 5 │
└──────────┴───────┘fn main() {
let mut s = String::from("hello");
println!("len={} capacity={}", s.len(), s.capacity());
s.push_str(", world");
println!("len={} capacity={}", s.len(), s.capacity());
println!("header size: {} bytes", std::mem::size_of::<String>());
}len=5 capacity=5
len=12 capacity=12
header size: 24 bytesThe header is always 24 bytes (three 8-byte numbers on a 64-bit machine), however long the text is. The capacity grows in jumps so that most pushes don't need a new allocation.
3. Assignment moves ownership
Here is the first big surprise.
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{s1}, world!");
}error[E0382]: borrow of moved value: `s1`
--> src/main.rs:4:16
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 | println!("{s1}, world!");
| ^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
3 | let s2 = s1.clone();
| ++++++++let s2 = s1; moves the String from s1 to s2. After the move, s1 is no longer valid, and the compiler refuses any use of it.
4. Why a move, and not a copy?
After let s2 = s1;, Rust copies only the 24-byte header. The text on the heap is not copied. Both headers would point to the same heap text:
s1 ┌─────┐
│ ptr ├──────┐ heap
└─────┘ ├──────► "hello"
s2 ┌─────┐ │
│ ptr ├──────┘
└─────┘Now think about rule 3. When the scope ends, both s1 and s2 would be dropped, and the heap text would be freed twice: a double free, one of the classic C bugs that corrupts memory. Rust's fix is simple: after the copy, s1 is treated as no longer valid. Only s2 owns the text, so it is freed exactly once. That is a move.
This is rule 2 in action: one owner at a time.
5. The new owner works normally
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{s2}, world!");
}hello, world!Moves are cheap: 24 bytes are copied, however large the text is. Rust never copies heap data implicitly. When you want a real copy, you ask for it with .clone(), covered in the next lesson.
6. Integers are copied, not moved
For types that live entirely on the stack, such as integers, copying is as cheap as a move and there is no heap data to free twice. So they are simply copied, and both variables stay valid.
fn main() {
let x = 5;
let y = x;
println!("x = {x}, y = {y}");
}x = 5, y = 5The exact rule is the Copy trait, explained in the next lesson.
7. The same Vec moves too
Vec has the same layout as String (pointer, length, capacity on the stack; elements on the heap), so it moves the same way.
fn main() {
let scores = vec![90, 85, 77];
let backup = scores;
println!("{:?}", scores);
}error[E0382]: borrow of moved value: `scores`
--> src/main.rs:4:22
|
2 | let scores = vec![90, 85, 77];
| ------ move occurs because `scores` has type `Vec<i32>`, which does not implement the `Copy` trait
3 | let backup = scores;
| ------ value moved here
4 | println!("{:?}", scores);
| ^^^^^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
3 | let backup = scores.clone();
| ++++++++8. Pushing a value into a Vec moves it
The Vec becomes the new owner of the String.
fn main() {
let mut names = Vec::new();
let name = String::from("Ferris");
names.push(name);
println!("added {name}");
}error[E0382]: borrow of moved value: `name`
--> src/main.rs:5:22
|
3 | let name = String::from("Ferris");
| ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait
4 | names.push(name);
| ---- value moved here
5 | println!("added {name}");
| ^^^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
4 | names.push(name.clone());
| ++++++++names now owns the text, and name is empty-handed. The fix is to use the value before moving it, or to read it back from the vector:
fn main() {
let mut names = Vec::new();
let name = String::from("Ferris");
println!("adding {name}");
names.push(name);
println!("names[0] = {}", names[0]);
}adding Ferris
names[0] = Ferris9. A Vec owns its elements
When the vector is dropped, it drops every element it owns. One owner at the top frees everything below it.
fn main() {
let total_bytes;
{
let lines = vec![
String::from("first line"),
String::from("second line"),
String::from("third line"),
];
total_bytes = lines[0].len() + lines[1].len() + lines[2].len();
} // `lines` is dropped here, and so are all three Strings
println!("the lines used {total_bytes} bytes of text");
}the lines used 31 bytes of text10. You cannot move an element out of a Vec by index
If let first = list[0]; moved the String out, the vector would be left with a hole: an invalid element it would later try to free. So Rust forbids it.
fn main() {
let list = vec![String::from("a"), String::from("b")];
let first = list[0];
println!("{first}");
}error[E0507]: cannot move out of index of `Vec<String>`
--> src/main.rs:3:17
|
3 | let first = list[0];
| ^^^^^^^ move occurs because value has type `String`, which does not implement the `Copy` trait
|
help: consider borrowing here
|
3 | let first = &list[0];
| +
help: consider cloning the value if the performance cost is acceptable
|
3 | let first = list[0].clone();
| ++++++++The next examples show the correct ways to take ownership of an element.
11. Taking ownership with remove and pop
Methods that take an element out of the vector hand you its ownership, and the vector no longer contains it.
fn main() {
let mut tasks = vec![
String::from("deploy"),
String::from("test"),
String::from("build"),
];
let last = tasks.pop().unwrap();
let first = tasks.remove(0);
println!("took '{first}' and '{last}'");
println!("left in the vector: {:?}", tasks);
}took 'deploy' and 'build'
left in the vector: ["test"]pop() returns an Option because the vector might be empty; unwrap() takes the value out of it. See Option for the proper ways to handle that.
12. A for loop over a Vec moves the Vec
fn main() {
let words = vec![String::from("one"), String::from("two")];
for word in words {
println!("{word}");
}
println!("{}", words.len());
}error[E0382]: borrow of moved value: `words`
--> src/main.rs:6:20
|
2 | let words = vec![String::from("one"), String::from("two")];
| ----- move occurs because `words` has type `Vec<String>`, which does not implement the `Copy` trait
3 | for word in words {
| ----- `words` moved due to this implicit call to `.into_iter()`
...
6 | println!("{}", words.len());
| ^^^^^ value borrowed here after move
|
note: `into_iter` takes ownership of the receiver `self`, which moves `words`
--> /rustc/<version>/library/core/src/iter/traits/collect.rs:310:17
help: consider iterating over a slice of the `Vec<String>`'s content to avoid moving into the `for` loop
|
3 | for word in &words {
| +for word in words gives the loop ownership of the vector, and each word owns one String for one iteration. Afterwards, everything has been dropped. You saw the fix, for word in &words, in A First Look at String & Vec. The & is borrowing, the topic of References & Borrowing.
13. Moving out inside a loop
The compiler notices a move that would happen again on the next iteration.
fn main() {
let report = String::from("quarterly numbers");
let mut outbox = Vec::new();
for _recipient in 0..3 {
outbox.push(report);
}
println!("{}", outbox.len());
}error[E0382]: use of moved value: `report`
--> src/main.rs:5:21
|
2 | let report = String::from("quarterly numbers");
| ------ move occurs because `report` has type `String`, which does not implement the `Copy` trait
3 | let mut outbox = Vec::new();
4 | for _recipient in 0..3 {
| ---------------------- inside of this loop
5 | outbox.push(report);
| ^^^^^^ value moved here, in previous iteration of loop
|
help: consider cloning the value if the performance cost is acceptable
|
5 | outbox.push(report.clone());
| ++++++++The first iteration moves report into outbox, so there is nothing left to move on the second. To send three copies, you would clone it, as shown in the next lesson.
14. A move in only one branch still counts
If a value might have been moved, the compiler treats it as moved. It will not let you use a value that is valid on some paths and gone on others.
fn main() {
let urgent = true;
let message = String::from("server down");
let mut pager_queue = Vec::new();
if urgent {
pager_queue.push(message);
}
println!("logged: {message}");
}error[E0382]: borrow of moved value: `message`
--> src/main.rs:8:24
|
3 | let message = String::from("server down");
| ------- move occurs because `message` has type `String`, which does not implement the `Copy` trait
...
6 | pager_queue.push(message);
| ------- value moved here
7 | }
8 | println!("logged: {message}");
| ^^^^^^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
6 | pager_queue.push(message.clone());
| ++++++++This is exactly the kind of "valid only if some condition held" bug that Rust rules out at compile time.
15. Giving a moved-from variable a new value
A moved-from mut variable is not dead forever. You can assign it a fresh value, and it becomes usable again.
fn main() {
let mut buffer = String::from("batch 1");
let mut sent = Vec::new();
sent.push(buffer); // buffer moved out
buffer = String::from("batch 2"); // buffer owns a new value
sent.push(buffer);
buffer = String::from("batch 3");
println!("sent: {:?}", sent);
println!("current buffer: {buffer}");
}sent: ["batch 1", "batch 2"]
current buffer: batch 316. Assigning a new value drops the old one
When you assign to a variable that still owns a value, the old value is dropped right there, before the new one is stored.
fn main() {
let mut status = String::from("starting");
println!("{status}");
status = String::from("running"); // "starting" is freed here
println!("{status}");
status = String::from("stopped"); // "running" is freed here
println!("{status}");
} // "stopped" is freed herestarting
running
stopped17. Dropping a value early with drop
drop(value) frees a value immediately, instead of waiting for the end of the scope. It works by taking ownership of the value, so the variable cannot be used afterwards.
fn main() {
let session_token = String::from("a1b2c3");
drop(session_token);
println!("{session_token}");
}error[E0382]: borrow of moved value: `session_token`
--> src/main.rs:4:16
|
2 | let session_token = String::from("a1b2c3");
| ------------- move occurs because `session_token` has type `String`, which does not implement the `Copy` trait
3 | drop(session_token);
| ------------- value moved here
4 | println!("{session_token}");
| ^^^^^^^^^^^^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
3 | drop(session_token.clone());
| ++++++++Dropping early is useful for large buffers you no longer need, and later for releasing locks as soon as possible (see Shared State).
18. Values are dropped in reverse order of creation
Values in one scope are dropped last-created-first. You can't see this with String, because it prints nothing when freed; Drop, Deref & Cow shows it with a type that does. The order matters because later values may depend on earlier ones.
fn main() {
let config = String::from("config");
let connection = format!("connection using {config}");
let request = format!("request over {connection}");
println!("{request}");
} // dropped in this order: request, connection, configrequest over connection using config19. A block can hand ownership out
A block's value is moved to whoever receives it. The value outlives the block because it now has a new owner.
fn main() {
let banner = {
let mut text = String::from("*** ");
text.push_str("SALE");
text.push_str(" ***");
text // moved out of the block into `banner`
};
println!("{banner}");
}*** SALE ***20. Tuples move their parts
A tuple containing a String is not a simple stack value, so it moves as a whole.
fn main() {
let user = (42, String::from("grace@example.com"));
let copy = user;
println!("{}", user.1);
}error[E0382]: borrow of moved value: `user`
--> src/main.rs:4:20
|
2 | let user = (42, String::from("grace@example.com"));
| ---- move occurs because `user` has type `(i32, String)`, which does not implement the `Copy` trait
3 | let copy = user;
| ---- value moved here
4 | println!("{}", user.1);
| ^^^^^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
3 | let copy = user.clone();
| ++++++++21. Partial moves out of a tuple
You can move one field out of a tuple and keep using the others. The tuple as a whole can no longer be used, because it is missing a part.
fn main() {
let user = (42, String::from("grace"), String::from("grace@example.com"));
let email = user.2; // moves only the email out
println!("id still usable: {}", user.0);
println!("name still usable: {}", user.1);
println!("moved email: {email}");
}id still usable: 42
name still usable: grace
moved email: grace@example.comUsing the whole tuple after the partial move is an error:
fn main() {
let user = (42, String::from("grace"), String::from("grace@example.com"));
let email = user.2;
let everything = user;
println!("{email}");
}error[E0382]: use of partially moved value: `user`
--> src/main.rs:4:22
|
3 | let email = user.2;
| ------ value partially moved here
4 | let everything = user;
| ^^^^ value used here after partial move
|
= note: partial move occurs because `user.2` has type `String`, which does not implement the `Copy` trait22. Destructuring moves each part to a new owner
fn main() {
let order = (String::from("ORD-7"), vec![String::from("lamp"), String::from("bulb")]);
let (id, items) = order;
println!("{id} has {} items: {:?}", items.len(), items);
}ORD-7 has 2 items: ["lamp", "bulb"]23. Integers inside a tuple are copied out
Moving applies only to parts that own heap data. Reading an integer field copies it and leaves the tuple complete.
fn main() {
let product = (String::from("mug"), 12, 4.5);
let quantity = product.1;
let price = product.2;
println!("{} x {} at {}", quantity, product.0, price);
println!("{:?}", product);
}12 x mug at 4.5
("mug", 12, 4.5)24. Realistic example: moving messages between queues
A worker takes messages from an inbox, processes them, and moves them to either the "done" list or the "dead letter" list. Every String has exactly one owner at every moment, and nothing is copied.
fn main() {
let mut inbox = vec![
String::from("charge card #1001"),
String::from("send receipt #1001"),
String::from("charge card #1002 INVALID"),
String::from("send receipt #1002"),
];
let mut done = Vec::new();
let mut dead_letters = Vec::new();
while !inbox.is_empty() {
let message = inbox.remove(0); // ownership moves from inbox to `message`
if message.contains("INVALID") {
dead_letters.push(message); // ...then to dead_letters
} else {
println!("processed: {message}");
done.push(message); // ...or to done
}
}
println!("done: {}", done.len());
println!("dead letters: {:?}", dead_letters);
println!("inbox empty: {}", inbox.is_empty());
}processed: charge card #1001
processed: send receipt #1001
processed: send receipt #1002
done: 3
dead letters: ["charge card #1002 INVALID"]
inbox empty: true25. Realistic example: partitioning log lines by level
Consuming a vector with for line in lines and pushing each line into one of two new vectors moves every line exactly once. The original vector is used up, which is fine because we no longer need it.
fn main() {
let lines = vec![
String::from("INFO server started"),
String::from("ERROR disk full"),
String::from("INFO request ok"),
String::from("ERROR timeout contacting db"),
];
let mut errors = Vec::new();
let mut others = Vec::new();
for line in lines {
if line.starts_with("ERROR") {
errors.push(line);
} else {
others.push(line);
}
}
println!("{} errors:", errors.len());
for e in &errors {
println!(" {e}");
}
println!("{} other lines", others.len());
}2 errors:
ERROR disk full
ERROR timeout contacting db
2 other lines26. Realistic example: building a document and handing it off
A function builds a String and returns it. The caller becomes the owner, then moves it into an archive. At the end, the archive owns every document, and dropping it frees them all.
fn render_invoice(number: u32, customer: &str, amount_cents: u64) -> String {
let mut doc = String::new();
doc.push_str(&format!("INVOICE #{number}\n"));
doc.push_str(&format!("Bill to: {customer}\n"));
doc.push_str(&format!("Amount due: {}.{:02}", amount_cents / 100, amount_cents % 100));
doc // ownership moves to the caller
}
fn main() {
let mut archive: Vec<String> = Vec::new();
let invoice = render_invoice(1001, "Acme Corp", 125_050);
println!("{invoice}\n");
archive.push(invoice); // ownership moves into the archive
archive.push(render_invoice(1002, "Globex", 9_999));
println!("archived {} invoices", archive.len());
println!("second is #1002: {}", archive[1].starts_with("INVOICE #1002"));
} // `archive` is dropped, freeing both invoicesINVOICE #1001
Bill to: Acme Corp
Amount due: 1250.50
archived 2 invoices
second is #1002: truepush_str(&format!(...)) needs the & because push_str reads text without taking ownership of it. That is a borrow, and it is where this section is heading. The next lesson covers the other half of the story first: copying and cloning.
Summary
- The owner frees the value at the end of its scope, at a known point.
b = amoves ownership;acan no longer be used (unless the type isCopy).v.push(x)movesxinto the vector.- Every value has exactly one owner at any moment.
- Cleanup happens automatically when the owner goes out of scope.
- Moves copy only the small stack header. Heap data is never copied implicitly.
- Using a value after it moved is a compile error (
E0382), even if the move happened in only one branch or a previous loop iteration. - You cannot move out of a
Vecby index (E0507); useremove,pop, or a consumingforloop. drop(x)frees a value early. Assigning a new value frees the old one.
Next: Move, Copy & Clone.
