Chapter 16

Memory and ownership

👋 Anyone can read and edit this exercise. Sign up to save your progress.

You've been using ownership for a while now without making a fuss about it. You moved Strings, borrowed slices, passed &mut references into functions, and handed values to Vec, HashMap, Option, Result, and your own structs. Ownership ties these moves, borrows, and container operations into Rust's memory-safety model without a garbage collector.

What the borrow checker buys you

Languages with manual memory management (C, C++) hand you the power to free memory yourself, and with it the power to free it twice, free it too early, or forget to free it at all. Languages with a garbage collector take that power back and spend runtime and memory tracking what's still alive. Rust takes a third path: before the program runs, the compiler checks where owned values are dropped and whether references outlive what they point at.

Three rules summarize the model you've already been using:

  1. Every value has exactly one owner.
  2. When the owner goes out of scope, the value is dropped.
  3. You can borrow a value without owning it, under the aliasing rule (many &, or one &mut, never both).

Rules 1 and 2 give each value one place where cleanup normally happens, which rules out double-frees in safe Rust. The aliasing part of rule 3 rules out data races because you can't write through one reference while reading through another. Lifetime checks supply the other half: a reference can't remain usable after its owner has been dropped.

The payoff is that "did I free this?" and "is this pointer still valid?" stop being questions you answer at 2am with a debugger. The compiler answers them for you on every build.

A first look at lifetimes

There's one more thing the borrow checker tracks, and it's the part that scares people by name long before it troubles them in practice: lifetimes.

A reference borrows a value, so it must not outlive that value. If it could, you'd have a reference pointing at memory that's already been cleaned up, which is a use-after-free bug.

Picture a function that tries to return a reference to its own local string:

fn dangling() -> &String {
    let s = String::from("temporary");
    &s   // ERROR: `s` is dropped at the end of this function
}

s is owned by dangling, so it's dropped the moment the function returns. A reference to it would dangle, so the compiler rejects the code outright. That's the whole intuition: a borrow has to stay valid for as long as it's used, and the compiler checks that span (the reference's lifetime) against the owner's.

Most of the time the compiler works lifetimes out on its own and you never write one. When you do start annotating them (usually when a struct holds a reference, or a function returns one of several borrowed inputs), the syntax looks heavy, but the question it answers is always the same: which owner does this reference depend on, and will that owner still be alive?

You don't need the syntax yet. For now, recognize the shape of the error and connect it to the same safety rule you've already been using.

This is a genuinely hard spot for most people learning Rust. If it hasn't fully clicked, that's expected. It usually clicks through use because each compiler error ties the lifetime back to values and scopes you can see.

Wrapping up

Ownership is the model underneath the moves, borrows, and references you've been using throughout the course.

The whole picture

Why it's worth the friction

C++ uses RAII and destructors for deterministic cleanup, while garbage-collected languages track which values are still alive at runtime. Rust adds compile-time ownership and borrowing rules, so safe Rust turns use-after-free, double-free, and data races into compile errors. The borrow checker can be frustrating while you're still learning its rules, but that trade, an argument with the compiler now instead of a crash later, is the core bet the language makes.

Next chapter 17Traits