Ownership & Moves
Ownership is the idea that makes Rust different from every language you have used before. It is how Rust frees memory without a garbage collector and without asking you to call free. Once ownership clicks, most compiler errors start to make sense.
In this lesson you will learn the three ownership rules, see what happens in memory when you assign a String to another variable, and find out why some values are copied while others are moved.
The stack and the heap
A running program keeps data in two places:
- The stack holds values whose size is known at compile time, such as an
i32or abool. Adding and removing stack values is very fast, and they are removed automatically when a function ends. - The heap holds data whose size can change or is only known at run time, such as the text in a
Stringthat grows as a user types. The program asks for heap memory, gets back an address (a pointer), and must give the memory back when finished.
Giving heap memory back exactly once, at the right time, is the hard part. Free it too early and other code reads garbage. Free it twice and the program can crash. Never free it and memory leaks. Ownership solves this.
The three ownership rules
- Each value in Rust has a variable that is its owner.
- A value has exactly one owner at a time.
- When the owner goes out of scope, the value is dropped (its memory is freed).
A scope is the region where a variable is valid, usually from its let to the closing } of its block. Rust calls a special cleanup function named drop at that point. To see it happen, the example below uses a small custom type that prints a message when it is dropped (you will learn struct and impl in Structs & Methods; for now just watch the messages):
struct Parcel {
label: String,
}
impl Drop for Parcel {
fn drop(&mut self) {
println!("Parcel {} dropped", self.label);
}
}
fn main() {
let first = Parcel { label: String::from("A-101") };
{
let second = Parcel { label: String::from("B-202") };
println!("Inside the block with {}", second.label);
}
let moved = first;
println!("End of main, holding {}", moved.label);
}Inside the block with B-202 Parcel B-202 dropped End of main, holding A-101 Parcel A-101 dropped
second is dropped at the end of the inner block. first was handed to moved, so only moved is dropped at the end of main, and the parcel is freed exactly once.
Moves
A String is made of three parts stored on the stack (a pointer to the text on the heap, a length and a capacity) plus the text itself on the heap:
stack: city heap
+----------+-------+ +---+---+---+---+---+
| pointer | ----+------> | K | o | c | h | i |
| length | 5 | +---+---+---+---+---+
| capacity | 5 |
+----------+-------+When you write let destination = city;, Rust copies only the three stack parts. Now two variables would point at the same heap text, and both would try to free it at the end of main: a double free. Rust prevents this by treating the assignment as a move: ownership passes to destination, and city can no longer be used.
fn main() {
let city = String::from("Kochi");
let destination = city;
println!("Going to {city}");
}error[E0382]: borrow of moved value: `city`
--> src/main.rs:4:25
|
2 | let city = String::from("Kochi");
| ---- move occurs because `city` has type `String`, which does not implement the `Copy` trait
3 | let destination = city;
| ---- value moved here
4 | println!("Going to {city}");
| ^^^^ value borrowed here after moveThe message tells the whole story: where the move happened, where you used the old name, and why (a String is not Copy). The compiler also suggests city.clone() as one possible fix. A move costs almost nothing: no text is copied, only three numbers.
Copy types and clone
Simple values that live entirely on the stack are cheap to copy, so Rust copies them instead of moving them. These types implement the Copy trait: all integers, floats, bool, char, and tuples or arrays made only of Copy types. Shared references (&T, next lesson) are Copy too.
When you really want a second, independent copy of heap data, ask for it with .clone(). A clone allocates new heap memory and copies the content, so it is slower, and the code shows it clearly.
fn main() {
let overs = 20;
let overs_copy = overs;
println!("{overs} and {overs_copy}");
let team = String::from("Lucknow Lions");
let backup = team.clone();
println!("{team} and {backup}");
let scores = [45, 61, 38];
let scores_copy = scores;
println!("{scores:?} {scores_copy:?}");
}20 and 20 Lucknow Lions and Lucknow Lions [45, 61, 38] [45, 61, 38]
overs and scores are Copy, so both variables stay usable. team is a String, so we cloned it on purpose; without .clone(), team would have been moved.
Ownership and functions
Passing a value to a function works exactly like assignment: a String argument is moved into the function, and a Copy value is copied. Returning a value moves ownership out to the caller.
fn shout(message: String) -> String {
message.to_uppercase()
} // `message` is dropped here
fn count_letters(word: String) -> (String, usize) {
let n = word.len();
(word, n) // give the String back along with the count
}
fn main() {
let order = String::from("two masala dosa");
let loud = shout(order);
println!("{loud}");
// println!("{order}"); // error: `order` was moved into `shout`
let name = String::from("Ananya");
let (name, letters) = count_letters(name);
println!("{name} has {letters} letters");
}TWO MASALA DOSA Ananya has 6 letters
After shout(order), order belongs to the function and is dropped when the function ends; to_uppercase built a brand-new String, which is moved out to loud. count_letters shows how clumsy it would be to hand ownership back every time you only want to look at a value. Rust has a much better tool for that, borrowing, which is the subject of References & Borrowing.
Common mistakes
- Using a variable after moving it. If you still need the value, pass a reference (
&name) instead, or clone it if you truly need a separate copy. - Cloning everything to silence errors.
.clone()makes the error go away but copies data every time. Ask first whether a borrow would do. - Thinking a move copies the data. A move copies only the small stack part; the heap data stays where it is and gets a new owner.
- Expecting integers to move. Integers,
bool,charandf64areCopy, so they are copied and the original stays usable.
Key points
- Every value has exactly one owner, and it is dropped when the owner goes out of scope.
- Assigning or passing a heap-owning value such as
Stringmoves it; the old variable can no longer be used. - Moves stop double frees and cost almost nothing, because only the pointer, length and capacity are copied.
Copytypes (integers, floats,bool,char, and tuples or arrays of them) are copied instead of moved..clone()makes an explicit deep copy; returning a value moves ownership to the caller.
Exercise
Predict which lines compile, then write a version that does compile. Create let a = String::from("Rohtak");, assign let b = a;, and print a. Also create let x = 7; let y = x; and print both. Fix the String part in two different ways: once with clone, once by printing only b.
Show solution
fn main() {
// Fix 1: clone, so both variables own their own String
let a = String::from("Rohtak");
let b = a.clone();
println!("{a} {b}");
// Fix 2: use only the new owner
let c = String::from("Rohtak");
let d = c;
println!("{d}");
// Integers are Copy, so this was always fine
let x = 7;
let y = x;
println!("{x} {y}");
}Rohtak Rohtak Rohtak 7 7
The original println!("{a}") after let b = a; fails with E0382 because the String moved. The integer lines always compile, because i32 is Copy.
Quick Quiz
1. When is a value's memory freed in Rust?
Rust calls drop automatically at the end of the owner's scope.
2. After let s2 = s1; where s1 is a String, what is true?
Assigning a String moves ownership; only the pointer, length and capacity are copied.
3. Which type is Copy?
Tuples made only of Copy types are Copy; String and Vec own heap memory and are moved.
4. What happens to a String passed by value into a function that does not return it?
The function becomes the owner, so the value is dropped at the end of the function.
Learn Rust with a mentor
Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
