← all lessons
Ownership & Borrowing · #4 of 13

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 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:

(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.

BEFOREAFTER let b = a;stackheapstackheapaptr · len 5”hello”amoved outbptr · len 5”hello”same buffer — not duplicated
A move copies the small stack handle, not the heap data, then invalidates the old owner. One buffer, one owner, exactly one 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:

Copy: both stay usable editable · real rustc
Open in Playground ↗ ready

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:

Move: the original is consumed editable · real rustc
Open in Playground ↗ ready

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: pay for a second buffer on purpose editable · real rustc
Open in Playground ↗ ready

.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).

Portrait of Bjarne Stroustrup, creator of C++.
Bjarne Stroustrup · 1950– The Danish computer scientist who created C++ and coined RAII — tying a resource's lifetime to a variable's scope. Rust inherited that automatic, scope-based cleanup as 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:

A function can swallow your value editable · real rustc
Open in Playground ↗ ready

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

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.