Chapter 2: Ownership
Remember the last time you debugged a memory leak? Maybe it was in JavaScript — an array that kept growing because you forgot to remove event listeners. Or in Python — objects piling up because circular references kept them alive past their usefulness.
Memory management is the problem every language tries to solve differently. Rust's answer is ownership.
The Three Rules
Rust's ownership system has three rules. Memorize them — they'll save your sanity:
- Each value in Rust has an owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value is dropped.
That's it. These three rules eliminate an entire category of bugs that have plagued programmers for decades.
What Does "Owner" Mean?
Let's look at some code:
fn main() {
let s1 = String::from("hello"); // s1 owns the String
let s2 = s1; // ownership moves to s2
// This won't compile! s1 is no longer valid:
// println!("{}", s1); // ERROR: borrow of moved value
println!("{}", s2); // This works — s2 owns the String
}
When we write let s2 = s1, the ownership of the String moves from s1 to s2. After this line, s1 is invalid. You can't use it anymore. This isn't a runtime error — it's a compile-time error. Rust won't let you write code that uses a value after its owner has transferred it.
This is radical. Most languages let you pass references around freely. Rust forces you to think about who owns what, and when.
Why This Matters
Consider what happens in a function:
fn main() {
let s = String::from("hello");
takes_ownership(s); // s's value moves into the function
// This won't compile! s is no longer valid:
// println!("{}", s);
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string goes out of scope here, memory is freed
In languages like JavaScript or Python, the parameter would be a copy (or a reference that the garbage collector handles). In Rust, the ownership moves into the function. When the function ends, the value is dropped. If you still need s in main, you can't use it — and Rust tells you this at compile time, not at 3am in production.
Moving Instead of Copying
You might wonder: "But what if I want to keep both copies?"
Good question. Rust gives you two options:
Option 1: Clone the value
let s1 = String::from("hello");
let s2 = s1.clone(); // s1 and s2 are both valid, both own their own data
clone() creates a deep copy. Both variables now own separate data. This costs memory and CPU — you're duplicating the entire String.
Option 2: Borrow instead of taking ownership
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // borrow s1, don't take ownership
println!("The length of '{}' is {}", s1, len); // s1 is still valid!
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s goes out of scope, but it didn't own the String, so nothing is dropped
We'll cover borrowing in detail in the next chapter. For now, just know that & means "reference" — you're letting a function use a value without taking ownership of it.
The Dropping Rule
When a variable goes out of scope, Rust automatically calls the drop function to clean up the memory. This happens at the end of every scope:
fn main() {
{ // s1 is not valid here, not declared yet
let s1 = String::from("hello");
// s1 is valid from this point forward
} // s1 goes out of scope here, memory is freed
}
You don't need to manually free memory. You don't need a garbage collector. The compiler inserts the drop calls for you, and they're guaranteed to run exactly once.
Why Ownership Matters
At this point, you might be thinking: "This seems complicated. Why would I want this?"
Because ownership eliminates:
- Memory leaks — if a value has one owner, and that owner can be tracked, leaks become impossible (except in specific unsafe patterns)
- Use-after-free — you can't use a value after its owner has been dropped
- Double-free — the drop happens exactly once, when the owner goes out of scope
- Data races — we'll talk about this in the concurrency chapter, but ownership is the foundation
And here's the thing: once ownership becomes intuitive, it stops feeling like a restriction. It starts feeling like relief. You write code, the compiler verifies it's correct, and you deploy with confidence.
The Mental Shift
The hardest part of ownership isn't learning the rules — it's unlearning the patterns from other languages. In Python, you pass objects to functions all the time without thinking about what happens to the original. In JavaScript, you mutate objects freely and let the GC clean up.
In Rust, every value has a clear lifecycle. Every assignment potentially moves ownership. Every function call potentially transfers ownership. The compiler tracks all of this, and if you get it wrong, it tells you — before you ship.
This is the foundation everything else builds on. Borrowing, lifetimes, traits — they all exist to help you express ownership patterns that would be impossible or dangerous in other languages.
Next: Chapter 3 — Borrowing