all_lessons/Rust/00 · Orientationlesson 1 / 23

Orientation: Rust makes the compiler keep the contract

The C++ track ended on a bargain: undefined behavior is the price of zero overhead, and the contract — never break these rules — is kept by nothing but the programmer's promise. The Go track answered a different question (what does complexity cost a team?) and kept the contract with a run-time system instead. This series asks the third question: could the compiler keep the contract, at C++'s price? Before any syntax, one claim: Rust is what you get when you take that question seriously and follow its consequences one forced step at a time. This lesson states the promise, shows why the design space collapses, installs a five-question habit, and draws the 23-lesson chain in which every lesson solves exactly the problem the previous one created.

The thesis, here
This is an original first-principles track — not a syntax tour and not a reference manual. Where C++ from the Machine Up trusts you and Go from the Team Up constrains you, Rust verifies you: every safety property that C++ leaves as a promise in your head becomes a fact the compiler must be able to check, and the price is paid at compile time, in the shape of your code. The lessons teach Rust in the order the promise forces it, so that by the end each rule reads as the only move that was left.
Linear position
Forced by: The C++ track ended on a bargain: undefined behavior is the price of zero overhead, and the contract is kept by the programmer's promise alone. Could the compiler keep the contract instead, at the same zero cost?
New idea: state the promise — safe code has no undefined behavior — and the budget — no garbage collector, no run-time system, no cost you cannot see — and show that together they leave exactly one place for the proof: compile time.
Forces next: Suppose safe code must never exhibit undefined behavior, with no garbage collector and no run-time checking beyond the cheap ones. "Undefined behavior" is a long list of different bugs, and a compiler can only reject what it can recognize. What exactly must be ruled out, and what does each rule need to know?
The plan
Six moves. (1) Recall what the C++ track left open and what Go paid instead — three answers to one problem. (2) State the promise and the budget precisely, including what the promise does not cover. (3) Derive the design by elimination: turn the dials yourself and watch the options collapse. (4) Install the habit — five questions for any line of code. (5) Draw the chain: 23 lessons, each forced by the one before. (6) Price the destination, and drop the misconceptions it invites.

1 · Three answers to one problem

The C++ track's last lesson made a claim worth rereading: undefined behavior is not a defect in the standard, it is the mechanism by which zero overhead is implemented. The compiler may assume you never break a short list of rules — never index out of bounds, never touch freed memory, never race — so it emits no check that would guard against you doing so. You keep your half of the contract by discipline, by review, by running sanitizers in test. If you break it, the standard owes you nothing at all.

Any language that lets you touch memory has to decide who keeps that contract. There are only three candidates: the programmer, the run-time system, and the compiler. The three language tracks in this library are the three answers. ("Run-time system" here means whatever runs beside the program to enforce the contract: a collector, a scheduler, checks inserted everywhere. Code the compiler emits that enforces nothing, such as the bookkeeping for a panic's unwinding in Lesson 10, is not one.)

C++ · trusts youGo · constrains youRust · verifies you
Who keeps the safety contractYou — it is a promise (Lesson 19 of that track)The run-time system: bounds checks, nil panics, a garbage collectorThe compiler — the contract is a proof it must be able to check
What it costs at run timeNothing: no defensive checks are emittedA bounded but real bill: a collector, a scheduler, bounds checksA few cheap checks; no collector, no scheduler
When a mistake shows upAt run time as undefined behavior — or neverAt run time as a panic; a data race only if you remembered to run the race detectorAt compile time, as an error you can read
What you give upThe safety netA little speed, control over when memory is released, and some type-system powerSome safe programs the checker cannot prove safe: you restructure them, or you write unsafe and prove it yourself

Nothing here says one column is right. It says they are three different places to spend the same scarce thing: someone's attention. C++ spends the programmer's. Go spends the machine's. Rust spends the compiler's — and yours, up front, learning to write programs the compiler can verify. Whether that is a good trade is a question about your project. What this series can do is show you exactly what the trade is, by deriving the language from the promise instead of listing its features.

2 · The promise and the budget

Two sentences define the whole design. Everything else in this series is a consequence of holding both at once.

The promise, and the budget
Promise. In safe Rust — the language without the keyword unsafe — no program can exhibit undefined behavior. Every operation whose behavior the language would otherwise leave undefined is either rejected at compile time or checked at run time.
Budget. No garbage collector. No run-time system the program did not ask for. No abstraction that costs more than the code you would write by hand. (There is one honest concession, and §3 makes you find it.)

Read the promise as a statement about programs, not about programmers: it does not say your code is correct, it says your code cannot be undefined. The distinction is the whole point. A wrong answer is a bug you can find with a test; undefined behavior is a bug the language has agreed not to describe — it can corrupt unrelated memory, be exploited, and vanish when you add a print statement (the C++ track's Lesson 19 showed the optimizer deleting a null check that came after the bug).

Here is the smallest program that shows what "the compiler keeps the contract" feels like. It takes a name for the first element of a vector, then grows the vector — which may move its buffer — then uses the name. In C++ this compiles, runs, and is undefined behavior. In Rust:

fn main() {
    let mut v = vec![1, 2, 3];
    let first = &v[0];      // a name for the first element
    v.push(4);              // may reallocate: moves the buffer
    println!("{first}");    // ...then uses the stale name
}
Reading the error
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let first = &v[0];
  |                  - immutable borrow occurs here
4 |     v.push(4);
  |     ^^^^^^^^^ mutable borrow occurs here
5 |     println!("{first}");
  |                ----- immutable borrow later used here
Notice what the message is made of: three places. Where a name was created, where something conflicting happened, and where the name is used afterwards. That triple — creation, conflict, later use — is the anatomy of nearly every borrow error you will ever read, and Lesson 07 shows it is not a coincidence but the definition of what the checker computes. Move the println! above the push and the same statements compile: nothing changed except that the old name is no longer used after the buffer moves. What the message cannot tell you is which repair you want: finishing with first before the push, cloning, or restructuring are all legal edits, and it only says where to look.
What the promise does not cover
Safe Rust does not prevent deadlocks, does not prevent memory leaks (leaking is safe), does not stop an integer from overflowing to a wrong answer (the behavior is defined — it wraps or panics — just possibly not what you meant), and does not make your logic correct. It rules out one family of failures: the ones where the language itself would have no idea what your program does. Two further honest qualifications: the promise holds provided the unsafe code it relies on is sound — the standard library's Vec is built on such code, and Lessons 19–20 are about who checks it — and it describes the language as designed; a compiler is a program with its own bugs (Lesson 22 takes stock of the soundness holes still open).

Both sentences are now on the table. Which designs can keep the first without breaking the second?

3 · Why the design space collapses

You can argue that the promise and the budget together force the design. Not by decree — by elimination. The C++ track's catalog of undefined behavior has seven families (out-of-bounds access, use-after-free, signed overflow, uninitialized reads, null dereference, data races, type punning). For each, ask: which mechanisms can actually rule it out, and what do they cost?

free of run-time cost
Trust the programmer (guarantees nothing). Local static rules: a variable must be assigned before use, absence is a type instead of a null, arithmetic has a defined answer, unrelated types cannot be cast. A static proof about who can reach what, and when.
a small per-operation cost
Cheap run-time checks: compare an index to a length, trap a null, zero a fresh variable. Dynamic tracking: keep a counter per object saying who is borrowing it, and panic on a violation.
a run-time system
A garbage collector keeps everything alive while it is reachable. Heavy instrumentation (sanitizer-style shadow memory) detects almost everything — at the roughly 2–10× slowdown the C++ track quotes for its sanitizers.

Now make the choice yourself. The slider is your run-time budget; the button says whether the safety must be a guarantee (the promise) or a hope (C++). A filled dot means "this mechanism really does rule that family out"; it turns hollow when your constraints forbid paying for it. The bottom row counts how many designs are left for each family. Start at the top of the budget and drag down.

Derive the design by elimination
Rows are ways a language can handle a bug family; columns are the seven families from the C++ track. Lower the run-time budget and watch the options for use-after-free and data race collapse to a single design. Then try the presets: which mechanism does each language actually use for each family?
families with a design
—
forced to exactly one
—
no design left
—
Show the core JS
// A mechanism rules a family out iff it fits the budget, guarantees that family,
// and (when a guarantee is required) is not "trust the programmer".
function eligible(m, f) {
  if (m.cost > st.budget) return false;
  if (m.id === 'trust') return !st.guar;
  return m.g.indexOf(f) >= 0;
}
// per family: the eligible mechanisms; 0 = no design, 1 = forced.
function options(f) { return MECH.filter(function (m) { return eligible(m, f); }); }

What you should have found. With the guarantee required and the budget at "cheap checks", the readout lists five families with exactly one design left. Three have a cheap one: a local rule for signed overflow and for type punning, a compare for out-of-bounds access. The other two — use-after-free / dangling and data race — are left with a static proof about which names can reach which memory, and when. Not because someone preferred it, but because the alternatives cost a collector (which the budget forbids, and which does not help with races anyway), a counter per access (which the budget forbids for every access), or the sanitizers' slowdown (roughly 2–10× in the C++ track). Drop the budget to zero and a different family goes red — out-of-bounds access — and that is the honest concession: even Rust's promise costs one compare per checked index. Everything the promise needs beyond that is free of run-time cost, because it happens before the program runs.

Road not taken · "just add a garbage collector"
A collector answers use-after-free and double free by never freeing something still reachable — Go's choice, and a good one under a different budget. It does not answer data races (the collector's row has no dot under data race), it moves the cost into a run-time system and its pauses, and it makes the moment of destruction unpredictable, so files and locks still need separate cleanup (the C++ track's RAII lesson made this point from the other side). Rust's budget rules it out.

The two families that collapse are not arbitrary. Every other family is a problem with a single operation — this index, this read, this cast — and a local rule can rule it out. These two are problems with a relationship between two names over time: one name frees or changes a place while another name still expects it to be there. No local rule can see that. The next lesson makes it precise, and the rest of the series is the machinery that makes it checkable without a run-time system. First, though, a habit that finds such relationships in any line of code.

4 · The habit: five questions for any line of code

The C++ track installed five questions about cost; the Go track installed five about complexity. This one installs five about proof. By the end you will ask them reflexively, in any language, and usually know the answer:

1. Who owns this, and when does it end?
Exactly one name is responsible for each value's life; when that name goes out of scope, the value is dropped. Ambiguity here is where double frees come from. (Lessons 03, 04.)
2. Who else can reach it — shared or exclusive, and for how long?
Many readers or one writer, never both, for exactly as long as the access can still be used. This is the law the whole language enforces. (Lessons 05–08.)
3. What does the signature promise?
Lifetimes, mutability, Send/Sync, fallibility: the contract is written in the type, so a caller can rely on it without reading the body. (Lessons 08–14, 16.)
4. Is this proven now, or checked later — and at what price?
A static proof is free at run time. A RefCell's borrow flag, a bounds check and an Arc's atomic counter each buy a guarantee while the program runs. Name the price. (Lessons 02, 15, 17.)
5. Where does the proof stop?
What would unsafe have to promise here, and who audits that promise? The answer marks the boundary between what the compiler knows and what a human vouches for. (Lessons 19–21.)

None of these is Rust-specific. They are what a careful reviewer asks of any C++ pointer, any Go channel, any Java shared list. Rust is the language that makes you answer them in the source, checked by a machine — which is how they become reflex. The order in which the series answers them is its spine.

5 · The 23-lesson spine is a derivation, not a topic list

Each lesson opens with the problem the previous one created and closes by creating the next. That relay is written down: the sentence that ends lesson N is the same sentence that begins lesson N+1, and the series index shows all of them in one table. The six movements: state the promise (I); derive the ownership-and-borrowing law that keeps it (II); build types that carry proofs, so abstraction does not cost the guarantee (III); handle the cases where one owner is not enough (IV); mark the boundary where a human takes over from the checker (V); and ship it, with an honest bill (VI).

#LessonThe one move it makes
I · The promise
00Orientationstate the promise and the budget; the only place left for the proof is compile time
01The promise, made precisefactor the seven bug families into cheap local fixes plus one law about names and places
02Places, values, and the stackfix the vocabulary; bounds, initialization and overflow become local rules
II · Ownership and borrowing — the law
03Ownershipone owner per value; its scope end is the one drop
04Moveassignment transfers ownership; the source is statically dead
05Borrowinglook without owning, under the law: shared readers or one exclusive writer
06Lifetimes as regionsa borrow is a region and must fit inside its owner's
07The borrow checkerregions are liveness on the control-flow graph
08Lifetimes in signatureswrite the input-to-output borrow relationship down, so checking is modular
III · Types that carry proofs
09Enums, Option, matchabsence and choice become types, with an exhaustiveness proof
10Errors: Result, ?, panicfailure is a value with a one-character propagation operator; bugs panic
11Traitsbehavior as a named, checked, globally-unique contract
12Generics and monomorphizationone definition, checked once, erased at zero run-time cost
13Trait objectsdynamic dispatch as an explicit, priced choice
14Closures and iteratorsthe same three ownership modes, and abstraction that compiles away
IV · When one owner is not enough
15Smart pointers and interior mutabilitythe same law, checked at run time, by choice
16Send and Syncthe law across threads, derived structurally by the compiler
17Concurrent programsmove over channels, lock to share, scope to borrow
18Asynca future is a compiler-generated state machine; Pin makes it sound
V · The trust boundary
19unsafeextra powers, explicit obligations, and soundness as the standard
20Building a safe abstractioninvariants plus privacy turn an unsafe core into a safe API
VI · Shipping, and the honest bill
21Crates, Cargo, and privacymake promises portable across teams
22Capstone and the honest costsassemble, price, and choose deliberately

Before the first step, price the destination: what thinking this way buys, and what it costs.

6 · What it buys, what it costs

What thinking this way buys
  • Whole bug classes that never reach a test. Use-after-free, iterator invalidation and data races in safe code are compile errors, not incidents (Lessons 05–07, 16).
  • Abstraction with little tax. Generics, closures and iterators, in many cases, compile to what you would write by hand (Lessons 12, 14); dynamic dispatch is the priced exception (Lesson 13).
  • Contracts you can read. A signature says who borrows what, for how long, and whether it may cross a thread (Lessons 08, 16).
  • A vocabulary for every language. Ownership, aliasing and lifetime questions arise in Go, Java and C++ too; here they are checked instead of hoped for.
What it costs (named honestly)
  • An up-front learning cost. You are learning to write programs a checker can verify; some correct programs are rejected until you restructure them (Lessons 07, 22).
  • Some shapes fight the law. Cyclic graphs, self-referential structs and shared mutable state need indices, Rc/RefCell, or unsafe (Lessons 15, 19–20).
  • Compile time and complexity. Build time is a recurring complaint in Rust's own surveys, and signatures say more than Go's do (Lessons 12, 22).
  • Async is a second dialect. Waiting costs bytes instead of threads, and comes with pinning and function colouring (Lesson 18).
Honest scope
This is not a claim that Rust is the right language for your next project, or that a collector-based language is a worse one — for many projects, paying the run-time bill and keeping the language simple is exactly right, and Go's track argues it well. The claim is narrower: someone is always keeping the safety contract, and an engineer who has watched a compiler keep it — and learned precisely what it needed to see — reasons better about every codebase afterward. We use Rust because it is the language that makes the contract visible, checkable and priced.

7 · Misconceptions to drop now

"Rust is C++ with a nicer syntax"
The syntax is the least of it. The difference is an entire second program — a proof — that runs on every build and treats its findings as errors. C++ has no analogue; the closest thing is a static analyzer you are free to ignore.
"The borrow checker is a linter"
A linter warns about what is probably wrong. The borrow checker rejects what it cannot show is right: it is sound but conservative, so it will refuse some programs that are in fact fine (Lesson 07). The remedy is to restructure, not to silence it.
"Rust has no run-time cost"
It has no hidden cost. You still pay for what you ask for: a bounds check, an Arc's atomic counter, a RefCell's borrow flag, a dynamic dispatch. The series prices each one.
"Safe means correct"
Safe means not undefined. A safe program can still deadlock, leak, overflow to the wrong number, or simply compute the wrong thing (§2). The promise is narrow on purpose, which is why it can be kept.

Checkpoint exercise

Try it
Recall a real bug you have met in any language that involved memory or concurrency — a dangling pointer, a ConcurrentModificationException, a data race, a stale cache entry. Write down, in one line each: the two names for the same thing, what one of them did (wrote? freed? moved?), and when the other used it. If you cannot find two names, your bug was a wrong answer rather than undefined behavior, and §2 says the promise does not cover it. Then try to state, in one sentence, the rule that would have made that bug impossible. Do not worry if the sentence is clumsy; you will write the exact version in Lesson 01, and you will find that the same sentence covers bugs that look nothing alike.

Where this points next

We have a promise, a budget, and the argument that together they leave only compile time for the proof. But "compile time" is not yet a design: a compiler can reject only what it can recognize, and "undefined behavior" is a long, heterogeneous list. Lesson 01 does the work that turns the slogan into a specification. It takes the seven families apart, shows that most yield to a small local rule, and shows that the two dangerous ones are really the same bug in different clothes — one law about names and places. Once you can state that law in a sentence, the rest of the series is how to make a machine check it. First, then: what exactly must be ruled out, and what does each rule need to know?

Takeaway
There are three places to keep the safety contract: the programmer (C++ trusts), the run-time system (Go constrains), and the compiler (Rust verifies). Rust's promise is that safe code has no undefined behavior; its budget is no garbage collector, no run-time system, no cost you cannot see. Held together, they eliminate every mechanism except a static proof for the two families that no local rule can see — use-after-free and data races — and the one honest concession is a cheap compare on each checked index. The promise is narrow (it is not correctness, and it does not prevent deadlock or leaks) and conditional (on sound unsafe underneath). The portable skill is five questions — who owns it, who else can reach it and how, what does the signature promise, is it proven or checked, and where does the proof stop — and the 23-lesson spine is a derivation in six movements that turns each into a reflex.

Interview prompts

Companion reads: C++ · 19 Undefined behavior (the bargain this lesson starts from), C++ · 05 RAII (why a collector does not end files and locks), and Go · 03 Pointers, the heap and the collector (the run-time system's answer, priced in its own terms).