Ownership: Who Owns the Data?
The single idea that makes Rust safe without a garbage collector
Every other language answers one question for you, quietly, millions of times a second: when is it safe to throw this data away? Get the answer wrong and your program either leaks memory until it dies, or frees something twice and corrupts itself.
Rust’s answer is a single word: ownership. Learn it and the rest of the language falls into place.
This is the lesson that makes Rust click — or makes you bounce off it for a week and come back. The mechanics are simple, but they overturn a common assumption: that let b = a; is always harmless and leaves a perfectly usable. In Rust, sometimes it does and sometimes it doesn’t, and the difference is mechanical, not arbitrary.
If this lesson is hard, that’s normal. Read it twice. Once you have the model, the rest of the curriculum gets easier.
A few more words you’ll need
These concepts come up constantly here and in the next lesson. Get the rough idea now; details follow.
- The stack is a small, fast region of memory for local variables of a fixed, known-at-compile-time size (numbers, booleans, references). Stack data is cleaned up automatically when the function it lives in returns.
- The heap is a larger, more flexible region for things that grow at runtime (text whose length you don’t know in advance, lists that change size). Heap data must be allocated (a chunk requested from the OS) and later freed (returned to the OS).
- Garbage collector (GC): a background process some languages (Python, JavaScript, Java, Go) run to find and free unused heap data for you. Convenient, but costs runtime and causes unpredictable pauses. Rust has no GC.
drop: the action Rust takes when a value’s owner goes out of scope — it frees the value’s resources (heap memory, files, locks). You almost never calldropby hand; the compiler inserts it.- Double-free: a bug where the same memory is freed twice. In C and C++ this corrupts memory and is a classic source of crashes and security holes. Rust makes it impossible at compile time.
The idea: every value has one owner
In Rust, every value belongs to exactly one variable at a time. We call that variable the value’s owner. When the owner goes out of scope — when the function or block where it lives ends — the value is automatically dropped: its resources released, its memory freed.
That’s it. No garbage collector, no manual free. The compiler reads your code, figures out where each variable’s scope ends, and inserts the cleanup there.
So far so simple. The interesting question is: what happens when you write let b = a;?
Two possibilities: copy or move
When you assign one variable to another, one of two things happens, depending on the value’s type:
- If the type implements the
Copytrait, the bits are duplicated.aandbare independent copies. Both stay usable. - If the type does not implement
Copy, ownership moves.bbecomes the new owner;ais consumed. Usingaagain is a compile error.
(A trait is a contract a type can satisfy — lesson 8 covers them properly. For now, think of Copy as a label some types wear meaning “safe to bit-for-bit duplicate.”)
The picture below is the whole lesson. A String is two parts: a small handle on the stack (a pointer, a length, a capacity) and the actual text bytes on the heap. When you move it, only the cheap handle is copied — and Rust then forbids the old handle, so two owners can never free the same heap buffer.
drop. That is how Rust guarantees no double-free, for free.Which types are Copy? The cheap, small, stack-only ones: all integers (i32, u64…), floats (f64, f32), bool, char, and tuples/arrays of Copy types. Which are not Copy? Anything that owns a heap allocation or another resource: String, Vec<T>, Box<T>, File. For those, duplicating the bits would give two handles to one buffer — and a double-free when both go out of scope. Rust’s fix: make assignment a move.
See the move error for yourself
Integers are Copy, so this is fine — a survives the assignment. Run it:
Now the same shape with a String, which is not Copy. Hit Run and read the borrow-checker error — this exact message is one you’ll meet a hundred times:
The compiler points at three things: where the move happened, why it moved (String isn’t Copy), and where you tried to use the dead value. Learn to read these slowly; they are the most helpful error messages in any mainstream language.
If you genuinely want two independent copies, ask for one out loud with .clone():
.clone() is loud on purpose. Cloning a String asks the OS for a fresh allocation and copies every byte — not free. Rust makes that cost visible by requiring you to type it, so you decide whether you really need two copies or could share one (next lesson).
drop, then added the borrow checker C++ never had to make it safe. Function calls move too
Passing a value into a function follows the same rule. Passing a String moves it in — the caller doesn’t get it back:
Uncomment the last line and run it again to watch the same move error appear. This would be unbearable if you had to clone before every call — which is exactly why the next lesson introduces borrowing: letting a function read or modify a value without taking ownership.
When does drop run?
When a value’s owner goes out of scope. The compiler decides where that is and inserts a hidden drop(value):
fn main() {
let s = String::from("hello"); // s owns a heap allocation
println!("{s}");
// <-- at this closing brace the compiler inserts drop(s);
// the String's heap buffer is returned to the OS.
}
If ownership has already moved out (to another variable or function), the drop happens at the new owner’s scope-end instead. The whole thing is decided at compile time; there’s no runtime process sniffing for unused data.
Why this is called an 'affine type system'
Ownership isn’t an ad-hoc invention; it comes from type theory. In a normal (structural) type system, you can use a value as many times as you like. Rust’s non-Copy values follow an affine discipline: each value can be used at most once as an owner — after a move, it’s gone. That idea descends from linear logic, introduced by the logician Jean-Yves Girard in 1987, and from the linear types the computer scientist Philip Wadler popularised in a 1990 paper titled, memorably, “Linear types can change the world.” A linear value must be used exactly once; an affine value at most once. Rust chose affine because letting a value simply go out of scope unused (and be dropped) is convenient and harmless. So the borrow checker you’re fighting is, underneath, a small theorem prover enforcing a forty-year-old idea from mathematical logic.
The first big mindset shift
In most languages you stop thinking about memory after your first week; the runtime handles it. In Rust you keep thinking about it — but only at the shape level: “who owns this, and when does it stop being needed?” You’re not writing malloc and free, you’re just naming the owner.
After a few weeks this stops feeling foreign and starts feeling clarifying. You can read a function’s signature and see, without running it, what it does with its inputs: keeps them, gives them back, or throws them away. That’s the property the borrow checker enforces.
Key takeaways
- Every value has exactly one owner. When the owner goes out of scope, the value is dropped.
let b = a;either copies (forCopytypes like numbers and booleans) or moves (for everything else).- After a move, the original is consumed — using it is a compile error.
- Function calls move values the same way assignment does, unless you borrow (next lesson).
.clone()explicitly duplicates non-Copydata. It’s loud on purpose because heap allocation is expensive.- Rust has no garbage collector. Memory safety is enforced at compile time through ownership — no runtime overhead.
One owner. One drop. That is the whole trick, and from it Rust derives a guarantee no garbage collector can match: memory safety with zero runtime cost.
The next lesson asks the obvious follow-up question. If moving a value hands it away completely, how do two parts of a program ever share the same data? The answer — borrowing — is the other half of Rust’s soul.