Beginner · Ownership: Rust's big idea · topic 11 of 50 · 35 min
The ownership rules and moves
Explanation
The three rules
- Each value in Rust has an owner: the variable that holds it.
- There can only be one owner at a time.
- When the owner goes out of scope, the value is dropped (its memory is freed).
Scope is the part of the code where a variable is valid: from the line where it is declared to the closing curly brace of its block. Drop means "clean up": for a String, the heap bytes are freed. Rust calls a special function named drop for you automatically at the closing brace.
{ // s is not valid yet
let s = String::from("chai"); // s is valid from here
println!("{s}"); // use s
} // scope ends: s is dropped, heap memory freedWhat happens on assignment: a move
Now the key question. What does let s2 = s1; do when s1 is a String? Rust copies only the stack part (pointer, length, capacity). Both variables would then point to the same heap bytes.
let s1 = String::from("dosa");
let s2 = s1;
STACK HEAP
s1: ptr --+ (no longer valid)
len 4 \
cap 4 \ +---+---+---+---+
+----------> | d | o | s | a |
s2: ptr ----/ +---+---+---+---+
len 4
cap 4If both s1 and s2 were valid, then at the end of the scope both would be dropped, and the same heap memory would be freed twice: a double free. Rust prevents this simply: after let s2 = s1;, the value has moved to s2, and s1 is no longer usable. Only s2 will drop the memory.
let s1 = String::from("dosa");
let s2 = s1;
println!("{s1}");
// error[E0382]: borrow of moved value: `s1`
// value moved here: let s2 = s1;
// value borrowed here after moveMoves are cheap
A move copies only 24 bytes (three numbers on a 64-bit machine), no matter whether the string has 4 letters or 4 million. The heap data is never copied by a move. So Rust is both safe and fast here.
Moves happen in many places
- Assignment: let b = a;
- Passing to a function: takes_it(a); (next topic but one)
- Returning from a function: the value moves to the caller.
- Putting into a collection: list.push(a); moves a into the list.
- A for loop over a collection by value: for x in list moves each item out, and list is used up.
Assigning a new value drops the old one
let mut s = String::from("old");
s = String::from("new"); // "old" is dropped right here
println!("{s}"); // newDrop runs in reverse order
When several variables go out of scope at the same brace, they are dropped in the reverse order of creation: the last one created is dropped first. This matters later, when one value refers to another.
Common mistakes and fixes
- Using a variable after moving it (E0382). Fix: use the new owner; or borrow with & instead of moving (module 4); or call .clone() if you really need two copies.
- Moving inside a loop: for i in 0..3 { send(msg); } moves msg on the first round. Fix: send(&msg) if the function can borrow, or msg.clone() if it must own.
- Thinking a move copies the heap data. It does not; it only transfers ownership.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
The ownership rules and moves · 1/7
Ownership has three rules. Each value has one owner. Only one owner at a time. When the owner goes out of scope, the value is dropped.
Prefer a video by a teacher? Find free videos on “rust ownership rules move semantics explained” on YouTube ↗
In real life: Handing an order to the kitchen
In a restaurant ordering system, an Order (with a heap-allocated list of dishes) is created at the counter and then moved into the kitchen queue. After the move, the counter code cannot accidentally change or re-send the same order: the compiler forbids touching it. Exactly one part of the program owns each order, and it is freed exactly once when the kitchen finishes it.
fn main() {
let order = String::from("2 masala dosa, 1 filter coffee");
let mut kitchen_queue: Vec<String> = Vec::new();
kitchen_queue.push(order); // order moves into the queue
// println!("{order}"); // error: borrow of moved value
println!("{:?}", kitchen_queue);
}Practice (0/3 done)
Does this compile? let a = String::from("idli"); let b = a; println!("{b}");
Which line causes the error? 1: let x = String::from("vada"); 2: let y = x; 3: let z = y; 4: println!("{y}");
Write a program that creates a String, moves it to a second variable, and then tries to print the first. Read the full compiler error. Then fix it in two different ways: (a) print the second variable instead, (b) use .clone() so both are valid.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Copy and CloneOwnership: Rust's big idea · same module
- Ownership and functionsOwnership: Rust's big idea · same module
- Interview questions: ownership, borrowing and lifetimesRust careers and interviews
- Variables: let, const and varJavaScript Programming (language in depth) · First steps: values, variables and operators
- Variables: giving names to valuesPython Programming · First steps in Python
- Box<T>: values on the heapClosures, iterators and smart pointers
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
When is a value dropped?
Answer: When its owner goes out of scope
Rule 3: the value is dropped when the owner goes out of scope.
What does let s2 = s1; do when s1 is a String?
Answer: Moves ownership to s2; s1 can no longer be used
Assignment of a non-Copy value moves it.
Why does Rust invalidate s1 after a move?
Answer: To prevent the heap data being freed twice
If both owned the data, both would drop it, causing a double free.
How much data does a move of a 1 MB String copy?
Answer: Only the small stack part (pointer, length, capacity)
A move copies the three stack numbers; the heap bytes stay where they are.
let mut s = String::from("a"); s = String::from("b"); What happens to "a"?
Answer: It is dropped when s is reassigned
Assigning a new value drops the old value immediately.
How does a function give ownership back to the caller?Related
Answer: By returning it
Returning a value moves its ownership to the caller.
How does Rust decide when to free heap memory?Related
Answer: Ownership rules let the compiler insert the free at the right point
Rust frees a value when its owner goes out of scope, decided at compile time.
How does Rust free memory?Related
Answer: Through ownership: a value is freed when its owner goes out of scope
Ownership rules make freeing automatic and predictable, and the compiler checks them.
What is the best fix for "returns a reference to data owned by the current function"?Related
Answer: Return an owned value
Move ownership out instead of borrowing a local.
From Interview questions: ownership, borrowing and lifetimes
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 2 more topics in “Ownership: Rust's big idea” before its exam: Copy and Clone, Ownership and functions.
- Module 3 exam: Ownership · 12 questions · 20 min, without help. 4 topics of this module still to finish first.
- Then the next module, Borrowing, slices and lifetimes. 50 topics left in the course before the final exam.