Ownership & Borrowing
Why this chapter exists
Here it is. The chapter every Rust book has and every Rust learner secretly dreads. The borrow checker. Ownership. The reason your friend who tried Rust last summer keeps tweeting about it.
Ownership is the trick that lets Rust be fast like C and safe like Java without using a garbage collector. It is not, in fact, that hard. The reason it feels hard is that almost no other language has it, so you have no muscle memory for it. Once you build that muscle memory — which will take, I dunno, a weekend? — the rest of Rust gets dramatically easier, because ownership is the lens through which the whole language was designed.
So take a breath. The compiler is on your side. Here we go.
The three rules
Ownership in Rust has three rules. They fit on a Post-it note:
- Every value has exactly one owner.
- When the owner goes out of scope, the value is dropped (memory freed, files closed, etc.).
- There can be either one mutable reference to a value, or any number of immutable references — but not both at the same time.
That’s it. The rest of the chapter is just exploring what those mean.
Move semantics
Let’s start with something that surprises everyone:
Run it. Rust will refuse, and the error message is gorgeous:
borrow of moved value:
s1
What happened? When you wrote let s2 = s1;, you didn’t copy the string — you
moved it. The owner used to be s1; now the owner is s2. s1 is no longer
a thing you’re allowed to use. The compiler caught you at compile time, before
this could turn into a use-after-free at 2am.
Why does Rust do this? Because a String owns a chunk of memory on the heap.
If both s1 and s2 pointed at it, and they both went out of scope, the
program would try to free that memory twice — which is exactly the kind of bug
Rust was invented to outlaw.
You have three ways to fix the snippet above. Try each:
There’s a fourth way, which is the most common one of all: borrow.
Borrowing — references that don’t move
If you don’t want to give up ownership, you can lend it. A reference is written
with &:
&greeting is an immutable reference. It says: “here, look at this value,
but don’t change it, and give it back when you’re done.” length_of borrows
the string just long enough to count its bytes. When length_of returns, the
borrow ends and greeting is still ours.
You can have as many immutable references as you want at the same time — they’re all read-only, so there’s no way for them to interfere with each other.
But the moment you want to change something, the rules tighten. To mutate
through a reference, you need an &mut reference, and Rust will let you
have exactly one of those at a time:
Notice three things had to line up:
messageislet mut, so the binding itself is mutable.- We passed
&mut message, an exclusive mutable reference. shouttakes&mut String, matching the reference type.
Try changing let mut to let and re-running. Try removing the &mut from
the call site. Each thing you change, Rust tells you exactly what to do next.
The big rule: no aliasing while mutating
This one trips up everyone the first time:
You can’t mutate book while viewer is alive, because if you could, viewer
might point at memory that just got moved (if the string grew and reallocated).
This isn’t theoretical — this is exactly the iterator-invalidation bug that
has shipped to production in C++ codebases more times than anyone wants to
admit.
The fix is to stop using viewer before you mutate:
The compiler figures out the lifetimes of borrows for you — this is called non-lexical lifetimes, in case you ever want to impress someone. A borrow lasts only as long as it’s actually used, not until the end of the block.
Copy types — the not-quite-owned values
You might be wondering: if let s2 = s1; moves the string, why does
let x = 5; let y = x; not break anything? Try it:
The answer: i32 and other small, fixed-size, stack-allocated types implement
the Copy trait. Assigning a Copy type makes a bit-for-bit copy. There’s
no heap memory to worry about, so making two copies doesn’t risk a
double-free.
Copy types include: all the integer types, f32/f64, bool, char,
tuples of Copy things, and references (&T is Copy, which is why you can
pass them around so freely). String, Vec, Box, HashMap — anything that
owns heap memory — is not Copy.
You can opt your own types into Copy if they’re made up of Copy fields. We’ll
do that in the types chapter.
Slices — borrowing parts of things
A slice is a reference to a contiguous chunk of a sequence — part of a
String, part of a Vec, part of an array.
&str (a “string slice”) is the type you get when you borrow part of a string,
and also the type of string literals like "hello". This is why functions
usually take &str instead of &String — &str works for both string
literals and borrowed Strings:
That return type, &str, comes with a hidden promise: the returned slice is
borrowed from the input. The compiler tracks this for you via lifetimes,
which get their own chapter later. For now, just notice that ownership and
borrowing extend naturally to substrings — there’s no copying, no allocation,
just a tiny (pointer, length) pair pointing into the original.
A worked example — fighting the checker, then winning
Let’s write a function that takes a sentence and uppercases the first word. Naive first attempt:
Run that. The compiler will tell you, in detail, that first is an immutable
borrow of s and you can’t also mutably borrow s while first is alive.
The fix is to get out of the immutable borrow before we mutate:
By returning an index instead of a slice, we end the immutable borrow before we start the mutable one. This is a very common pattern when learning Rust: “oh, I just need to copy the small thing out and stop borrowing.” Once you start seeing it, the borrow checker stops feeling like an adversary and starts feeling like, well, a colleague pointing out a subtle bug before it ships.
What we’ve covered
You now know:
- Every value has one owner. Assignment moves ownership.
- You can borrow with
&(any number) or&mut(exactly one, exclusively). Copytypes are exempt — they’re cheap to duplicate.- Slices are borrowed views into sequences.
- The compiler is not your enemy. It is your overly polite, slightly pedantic pair programmer.
That’s it. That’s the bear. Most other Rust concepts — lifetimes, traits, even the smart pointers — are footnotes on the rules above. Get a feel for these and you’ve already done the hardest 60% of learning the language.
Next chapter: we name things. Types and traits.