Skip to content

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:

  1. Every value has exactly one owner.
  2. When the owner goes out of scope, the value is dropped (memory freed, files closed, etc.).
  3. 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:

🦀 main.rs

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:

🦀 main.rs

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

🦀 main.rs

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

🦀 main.rs

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:

🦀 main.rs

Notice three things had to line up:

  1. message is let mut, so the binding itself is mutable.
  2. We passed &mut message, an exclusive mutable reference.
  3. shout takes &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:

🦀 main.rs

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:

🦀 main.rs

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:

🦀 main.rs

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.

🦀 main.rs

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

🦀 main.rs

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:

🦀 main.rs

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:

🦀 main.rs

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).
  • Copy types 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.