Rust Ownership
Ownership is the idea that makes Rust Rust. It’s how the language guarantees memory safety without a garbage collector and without you manually freeing memory. It feels unusual at first, but the core idea is simple. Let’s build it up slowly.
The problem ownership solves
Section titled “The problem ownership solves”When a program runs, it stores data in memory. At some point that memory needs to be cleaned up, or your program leaks memory and eventually crashes. Languages handle this in two common ways:
- Manual (C/C++): you free memory yourself. Forget to, and you leak. Free too early, and you get crashes and security bugs.
- Garbage collector (Java, Go, Python): a background process cleans up for you. Convenient, but it costs performance and predictability.
Rust takes a third path: it decides at compile time exactly when each value should be cleaned up, using a set of rules about ownership. No manual freeing, no garbage collector.
The three rules of ownership
Section titled “The three rules of ownership”These are the whole system. Everything else follows from them:
- Each value has a single owner (a variable that “owns” it).
- There can only be one owner at a time.
- When the owner goes out of scope, the value is automatically cleaned up.
Let’s see each rule in action.
Rule 3: values are dropped at the end of scope
Section titled “Rule 3: values are dropped at the end of scope”A scope is the region between { and }. When a variable’s scope ends, Rust automatically frees its value — you never write free():
fn main() { { let message = String::from("hello"); // message is valid here println!("{message}"); } // <- scope ends, `message` is automatically cleaned up
// println!("{message}"); // ❌ error: message no longer exists here}You didn’t have to free anything — Rust did it the moment message went out of scope.
Rules 1 & 2: the “move”
Section titled “Rules 1 & 2: the “move””Here’s where it gets interesting. Watch what happens when you assign one variable to another:
fn main() { let s1 = String::from("hello"); let s2 = s1; // ownership MOVES from s1 to s2
println!("{s2}"); // ✅ works // println!("{s1}"); // ❌ error: s1 was moved}When you write let s2 = s1, Rust doesn’t copy the string — it moves ownership from s1 to s2. Because rule 2 says there can only be one owner, s1 is now invalid. Trying to use it gives:
error[E0382]: borrow of moved value: `s1`What if I want two copies?
Section titled “What if I want two copies?”If you actually want a second, independent copy, call .clone():
fn main() { let s1 = String::from("hello"); let s2 = s1.clone(); // makes a full copy
println!("s1 = {s1}, s2 = {s2}"); // ✅ both work now}clone() is explicit on purpose — copying can be expensive, so Rust makes you ask for it.
Simple types are copied, not moved
Section titled “Simple types are copied, not moved”You might wonder why this doesn’t cause an error:
fn main() { let x = 5; let y = x; println!("x = {x}, y = {y}"); // ✅ both work}Small, fixed-size values like integers, floats, booleans, and char are cheap to copy, so Rust just copies them instead of moving. As a beginner, the rule of thumb is:
- Simple values (numbers,
bool,char) → copied. The original stays valid. - Owned data (like
String) → moved. The original becomes invalid.
Ownership and functions
Section titled “Ownership and functions”Passing a value to a function moves it too — just like assigning to a variable:
fn take_ownership(s: String) { println!("I now own: {s}");} // s is dropped here
fn main() { let text = String::from("hello"); take_ownership(text); // println!("{text}"); // ❌ error: text was moved into the function}This is exactly why the next topic — borrowing — exists. Borrowing lets a function use a value without taking ownership of it, so you can keep using it afterward.
Practice
Section titled “Practice”Try each one yourself first, in the Rust Playground or a local project. Compiler errors are expected along the way: read them, they usually point right at the move.
1. Keep the original
Section titled “1. Keep the original”This program doesn’t compile. Fix it so it prints draft -> draft v2, without removing either variable.
fn main() { let original = String::from("draft"); let mut edited = original; edited.push_str(" v2"); println!("{original} -> {edited}");}error[E0382]: borrow of moved value: `original` --> src/main.rs:5:16 |2 | let original = String::from("draft"); | -------- move occurs because `original` has type `String`, which does not implement the `Copy` trait3 | let mut edited = original; | -------- value moved here4 | edited.push_str(" v2");5 | println!("{original} -> {edited}"); | ^^^^^^^^ value borrowed here after moveHint: you need two independent strings, so that changing one doesn’t touch the other.
Show solution
fn main() { let original = String::from("draft"); let mut edited = original.clone(); // a full, separate copy edited.push_str(" v2"); println!("{original} -> {edited}");}Output:
draft -> draft v2let mut edited = original; moves the string, so original is no longer valid. .clone() makes a second string with its own memory. Now push_str changes only edited, and original still says draft.
2. Give it back
Section titled “2. Give it back”Write a function add_signature that takes ownership of a String, adds " -- Ferris" to the end, and hands the string back. Call it from main and print the result.
Hint: a function can return the value it was given. Write -> String in the signature, and take the parameter as mut text: String so you’re allowed to change it.
Show solution
fn add_signature(mut text: String) -> String { text.push_str(" -- Ferris"); text // ownership moves back out to the caller}
fn main() { let letter = String::from("See you soon!"); let letter = add_signature(letter); println!("{letter}");}Output:
See you soon! -- FerrisOwnership moves into the function with the call, and moves back out with the return value. We catch it in a new letter variable (shadowing the old one). This works, but passing values in and out all the time gets tiring. That’s exactly the problem borrowing solves.
3. Who owns it now?
Section titled “3. Who owns it now?”This program doesn’t compile. Fix it so it prints Ana joined the list and then ["Ana"].
fn main() { let mut names = Vec::new(); let name = String::from("Ana"); names.push(name); println!("{name} joined the list"); println!("{:?}", names);}error[E0382]: borrow of moved value: `name` --> src/main.rs:5:16 |3 | let name = String::from("Ana"); | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait4 | names.push(name); | ---- value moved here5 | println!("{name} joined the list"); | ^^^^ value borrowed here after moveHint: push takes ownership, just like a function call. Who owns the string after line 4? Either use name before the move, or ask the new owner.
Show solution
fn main() { let mut names = Vec::new(); let name = String::from("Ana"); println!("{name} joined the list"); // use it while we still own it names.push(name); // now the vector owns it println!("{:?}", names);}Output:
Ana joined the list["Ana"]After names.push(name), the vector is the owner, and name can’t be used anymore. Printing first is one fix. Another is to keep the original order and print names[0] instead, because the vector still has the string. Cloning would also work, but there’s no need to copy here.
- Ownership lets Rust manage memory safely with no garbage collector.
- Three rules: one owner per value, one owner at a time, value dropped when the owner leaves scope.
- Assigning owned data (like
String) moves it — the original becomes invalid. - Use
.clone()for an independent copy; simple types (numbers,bool,char) are copied automatically. - Passing a value to a function moves it too — which leads us to borrowing.
Quick check
Section titled “Quick check”Quick check
1. How many owners can a value have at one time in Rust?
2. What method do you call to make an independent copy of a String instead of moving it?
3. Which of these types is copied automatically instead of moved when assigned to another variable?
4. What happens when you pass a String to a function that takes it by value (not by reference)?
5. When does Rust automatically clean up (drop) a value, according to this page?
Score: 0 / 5
Read more
Section titled “Read more”- What Is Ownership? — The Rust Book: the official chapter, with pictures of what a
Stringlooks like in memory and why a move doesn’t copy it. - Ownership and moves — Rust by Example: short, runnable examples of moves, including moving into functions.
- RAII — Rust by Example: how values are cleaned up when they go out of scope, and how the
Droptrait fits in. Copy— standard library docs: which types are copied instead of moved, and the difference betweenCopyandClone.- Rust track — Exercism: free practice problems with feedback, once you want more exercises than this page has.