Handbooks  /  Rust
Handbook~16 min readSystemsworked math + runnable code
The Rust Handbook

One owner,
checked at compile.

Most languages pick one of two deals for memory: a garbage collector that's safe but pauses and costs, or manual malloc/free that's fast but a minefield of double-frees and use-after-frees. Rust refuses the choice. Its one big idea — ownership — lets the compiler know exactly when every value is done, so it frees memory for you, with no collector and no crashes. The rules are strict and the famous "fight with the borrow checker" is real — but it's the compiler catching, before your program ever runs, bugs other languages ship to production. This handbook is those rules, modeled and runnable.

01

The third option

Memory management has long been a two-way trade. A garbage collector (Java, Go, Python) is safe — it frees memory for you — but it runs at runtime, causing pauses and overhead, and you don't control exactly when things happen. Manual management (C, C++) is fast and precise, but a single mistake — freeing twice, using memory after freeing, forgetting to free — is a crash or a security hole. For decades those were your only options.

Rust invents a third. Its rule of ownership says every value has exactly one owner (a variable), and when that owner goes out of scope, the value is freed — automatically, but at a moment the compiler can determine statically. So there's no garbage collector (the freeing is compiled in, not traced at runtime) and no manual free (you never write it). Because ownership is always singular and its end is known at compile time, memory is freed exactly once, never too early, never leaked. You get GC-level safety with manual-level performance, and the price is paid at compile time by you, not at runtime by your users. Everything else in Rust's memory model — moves, borrows, lifetimes — exists to make that one-owner rule enforceable.

The one-sentence version

Every value has exactly one owner freed when the owner's scope ends; assigning/passing moves ownership (invalidating the original), and to share you borrow — many shared XOR one mutable — all checked at compile time, so memory safety costs nothing at runtime.

02

Moves invalidate

Since a value can have only one owner, what happens when you assign it to a second variable or pass it to a function? Rust moves it: ownership transfers to the new binding, and the original binding becomes invalid. Try to use the moved-from variable and the compiler stops you with a "value moved here" error — before the program ever runs.

This looks harsh (in most languages b = a leaves both usable) but it's exactly what prevents a whole class of bugs. If both a and b owned the same heap value, then when each went out of scope, each would try to free it — a double-free, a classic memory-corruption vulnerability. By invalidating the original on a move, Rust guarantees there's always exactly one owner to do the freeing. When you genuinely need both to stay valid, you have two honest choices: borrow (take a temporary reference without transferring ownership) or clone() (make a real, independent copy so each owns its own value). The default being "move, not copy" is what makes single ownership hold without any runtime bookkeeping — the compiler tracks it and then erases the tracking.

03

Borrowing's one rule

Moving everything would be painful, so Rust lets you borrow — access a value through a reference without taking ownership. The borrow checker enforces a single rule that, remarkably, eliminates data races entirely:

The borrowing rule: shared XOR mutable
&sharedAny number of immutable references at once — everyone's only reading, so it's safe.
&mutExactly one mutable reference, and no shared ones — the writer has exclusive access.
forbiddenA mutable borrow while shared borrows exist (or two mutable) → won't compile.
resultNo two references can observe a value mid-write → data races impossible.

The logic is airtight: many readers is safe because nobody mutates; one writer alone is safe because nobody else can see a half-written state; a writer plus readers or two writers is a data race, so it's forbidden. Rust checks this statically, which means an entire category of concurrency bugs — the ones that show up as heisenbugs in other languages — literally cannot be expressed in compiling Rust. And it needs no runtime locks for the guarantee itself; the compiler proved the access pattern safe before the program started. "Shared xor mutable" is the whole borrow checker in four words, and it's why Rust can promise "fearless concurrency."

04

The ownership rules

A move transfers ownership and invalidates the source; a clone copies so both stay valid:

move: owner(dst) ← value,  owner(src) ← ∅  ⟹  use(src) = compile error     clone: both keep a valid value

After b = a (move), a is invalid; after b = a.clone(), both a and b are valid (two independent values).

The borrow checker grants a new borrow only if it's compatible with active ones — shared coexist, mutable demands exclusivity:

can_borrow(shared)  ⟺  no &mut active     can_borrow(mut)  ⟺  no &mut and no &shared active

Many shared borrows: fine. A mutable borrow while any borrow exists: rejected. The runnable version below models moves, validity, borrowing, and clone.

RUN IT YOURSELF

The borrow checker, modeled

Rust's ownership rules are just logic, so we can model them as pure functions. Moving a value transfers ownership and invalidates the source — using it afterward is the compile error Rust gives you. Borrowing follows one rule: any number of shared references, or exactly one mutable reference, never both — which is why data races can't compile. And clone() deep-copies, so both bindings stay valid. Change the moves and borrow counts and watch which uses are legal — this is the borrow checker's decision, made visible.

CPython · WebAssembly
05

Why it's worth it

Ownership feels like extra work because it is — you're doing, up front, the reasoning that other languages either defer to a runtime GC or leave to chance. The payoff is what that reasoning buys:

GuaranteeWhich rule provides it
No double-freeSingle ownership + move invalidation — only one binding ever frees a value.
No use-after-freeLifetimes — a reference can't outlive the value it points to (won't compile).
No data racesShared-xor-mutable borrowing — no writer while others read.
No GC pausesFreeing is compiled in at scope end, not traced at runtime — predictable performance.
No null surprisesOption<T> instead of null — the compiler forces you to handle "absent."

That combination — memory-safe and as fast and predictable as C — is why Rust is being adopted for the places both properties matter at once: operating-system kernels, browser engines, databases, embedded devices, and increasingly the performance-critical core of higher-level systems. The borrow checker is a strict teacher, and beginners do spend time fighting it. But that fight is you and the compiler finding, at build time, the exact bugs — dangling pointers, races, double-frees — that other languages discover in production at 3am. Once the model clicks, you stop fighting and start relying on it: if it compiles, an enormous class of memory and concurrency bugs is simply gone.

06

Pitfalls

The first is cloning to silence the borrow checker. When a move or borrow error appears, sprinkling .clone() makes it compile — but each clone is a real copy with real cost, and reaching for it reflexively throws away the performance you chose Rust for. Clone when you genuinely need two independent values; otherwise, restructure so a borrow suffices. The error is usually telling you something true about your data flow.

Two more. Fighting instead of listening: beginners treat borrow-checker errors as the compiler being pedantic, but almost every one is pointing at a real aliasing or lifetime problem — the fix is to understand what it's protecting you from, not to bludgeon it into submission. And reaching for unsafe or Rc/RefCell too early: Rust has escape hatches (unsafe blocks, reference-counting with Rc, runtime-checked borrows with RefCell) for the rare cases the static rules are too strict — but using them to dodge a borrow error you don't understand just moves the bug from compile time to runtime, discarding the guarantee. Learn the ownership model first; the escape hatches are for when you've hit a genuine limit, not a learning curve. The whole handbook reduces to one idea worth internalizing: a value has one owner, moving hands it over, borrowing shares it under "shared xor mutable," and the compiler proves it all before your program runs — which is why Rust is safe and fast at the same time, a combination that used to be a contradiction.

Worth knowing

Don't .clone() to silence the borrow checker — it copies at real cost; restructure to borrow instead. Read the errors as real aliasing/lifetime problems, and save unsafe/Rc/RefCell for genuine limits, not for dodging rules you haven't learned yet.

Frequently asked

Quick answers

What is ownership?

Every value has exactly one owner; when the owner's scope ends, the value is freed automatically — no GC, no manual free, and never freed twice.

What does "move" mean?

Assigning or passing a value transfers ownership and invalidates the original binding, preventing double-frees. Use borrow or clone to keep both valid.

What is the borrow checker?

It enforces "shared xor mutable": any number of immutable references, or exactly one mutable reference, never both — so data races can't compile.

How is there no GC?

The compiler knows each value's single owner and inserts the free at scope end — safety is proven at compile time and erased, so runtime pays nothing.

Finished this one? 0 / 99 Handbooks done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.