Table of Contents
Ownership: The Secret of Rust's Power#
In C, memory is like a shared pizza. Anyone can grab a slice, and anyone can throw the whole thing away. When you reach for it, you never know whether the pizza is still on the table — or whether it is already in the trash.
This chaos has broken countless developers late at night. Today we will see how Rust turns the dangerous pizza into a relay race, with a set of rules called ownership, where the baton never drops.
The shared pizza#
Look at this perfectly legal C code:
char *pizza = malloc(50);
process_data(pizza); // frees the pizza internally
free(pizza); // double free — the compiler stays silent
Three lines, and the compiler will not give you a single warning. But if process_data already freed the memory internally, the second free hands the allocator a pointer to a block that no longer exists. This is a double free — undefined behavior, a time bomb.
Or consider the opposite failure. If everyone assumes someone else will take out the trash, then nobody does. The pizza rots on the table while the program keeps running: a memory leak, invisible and accumulating, until the process eventually suffocates.
In C, nothing stops you from doing any of this. The language hands you the keys to the kitchen and trusts you to remember every slice.
Rust's relay race#
Rust replaces the pizza with a relay race, and the race has three hardcore rules:
One: every value has a variable that owns it.
Two: there can only be one owner at a time.
Three: when the owner goes out of scope, the value is dropped — automatically, no free required.
Watch what happens when the baton is passed:
let s1 = String::from("relay baton");
let s2 = s1; // the baton moves — s1's hands are now empty
For heap data like a String, Rust does not copy the underlying bytes. It moves the ownership. s1 is not left holding a stale duplicate — it becomes invalid, instantly, and using it again is a compile-time error.
Small values like integers are different: they are Copy types, cheap enough to duplicate, so assignment just copies the number. But anything that lives on the heap is handed over, not photocopied.
The compiler slaps your hand#
Try to use s1 after the move and the compiler does not negotiate:
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:4:16
|
2 | let s1 = String::from("relay baton");
| -- move occurs because `s1` has type `String`
3 | let s2 = s1;
| -- value moved here
4 | println!("{}", s1);
| ^^ value borrowed here after move
It looks annoying, right? But think of it as a free mentor standing behind you with a ruler. This single rule logically eliminates double frees — the old owner simply stops existing — and forces you to fix the bug before your code ever runs. Not at 3 AM, in production, during a crash. At your desk, while compiling.
Borrowing: lend a photo, not the baton#
What if you do not want to lose ownership? You borrow.
By using the & symbol, you create a reference. It is like lending a friend a photo of the baton instead of the baton itself:
let baton = String::from("relay baton");
let photo1 = &baton; // many readers may look at once
let photo2 = &baton; // still fine — photos are cheap
let pencil = &mut baton; // one writer — now photo1 and photo2 are off limits
They can look — an immutable reference — but they cannot change it. And Rust's iron rule: many people may hold photos at the same time, but the moment someone gets the right to modify, nobody else — not even the readers — may access the value during that time. One writer, or many readers. Never both.
The philosophy#
This is the philosophy of Rust ownership.
Since humans cannot manage memory perfectly, give the power to the rules. It is better to suffer while compiling than to wake up at 3 AM to debug a crash caused by a pizza that was thrown away twice.
Ownership is your first hurdle in the Rust world — but it is also your strongest line of defense.
