What "compiling" actually means
In part 2 we got one line of Rust all the way to an understood program: characters became tokens, tokens became a tree, and the tree got checked until it provably made sense. At this point every tutorial I ever read says the same five words: "then it compiles to machine code." One arrow on a whiteboard. Source on the left, binary on the right.
That arrow is hiding an entire pipeline. Describing what happens next as "it compiles" is like describing a flight as "then the plane goes up and comes down." Technically true, and you'd learn more from the boarding process. So this part is about what's actually inside the arrow.
The four-step story everyone gets taught
If you took a C course, you probably met the classic four steps: preprocess, compile, assemble, link. It's worth thirty seconds because it's real, and because it shows where the interesting stuff hides.
The preprocessor is glorified copy-paste: it splices your #include files into your source and expands macros, all as raw text, before anything meaningful happens. The compiler turns the resulting source into assembly. The assembler turns assembly into object files, which are basically machine code with holes in it, where the holes say "the code for printf goes here, I don't have it." And the linker fills the holes, stitching your object files and your libraries into one runnable executable.
Useful map. Also a very C-shaped map. Rust doesn't have a textual preprocessor at all, and the step labeled "compiler" is doing so much work that giving it one box is almost dishonest. Everything I found fascinating lives inside that box.
The problem: trees are good at meaning, bad at machinery
Here's the tension that explains the whole design. The tree from part 2 is fantastic for answering meaning questions: is this name defined, do these types match. But now we need to do machine work: squeeze out wasted operations, decide what lives in which register, produce instructions. And a tree is a miserable shape for that, the same way an essay is a miserable shape for a checklist. The information is all there, but not in a form the next job can use.
So compilers do something that sounds wasteful and is actually the whole trick: they keep rewriting your program into different forms. Each form throws away some human comfort and becomes easier for the machine to reason about. Compiler people call these forms intermediate representations, IRs, and they call each rewriting step lowering. You start at your level and descend, one deliberate step at a time, toward the CPU's level.
Riding one loop down the pipeline
The example that made lowering click for me is a plain for loop, because a for loop is pure human convenience. for item in items { process(item); } reads like English. No CPU on earth has a "for each" instruction. Somewhere between me and the silicon, that comfort has to be dismantled. In Rust you can watch it happen in stages.
Follow the loop down. In the AST it's still your for loop, structured but recognizable. One level down, in what Rust calls the HIR, the convenience has been unwrapped: there is no for loop anymore, just a bare loop that asks an iterator for the next value, runs your body if it got one, and breaks if it didn't. Nothing you couldn't have written yourself, which is the point. The "for" was a courtesy, and courtesies get removed first.
Another level down, the MIR, and now it doesn't even look like a program you'd write. It looks like a flowchart: little blocks of dead-simple operations connected by jumps. Do this, then jump there. Check this, jump one of two places. Every value's movement is explicit. And this is my favorite detail in the whole pipeline: Rust's famous borrow checker runs here, not on your source code. Checking "does this reference outlive the thing it points to" means tracing every path a value can take through the program, and that's a flowchart question. Rust literally built the perfect representation for its hardest check, then does the check where it's easiest.
Why the middle layers pay rent
Each representation earns its place by making one kind of work almost free. The tree made meaning-checks easy. The flowchart made path-tracing easy. And the next form down makes optimization easy. Remember our line from part 2, let answer: i32 = 40 + 2;? Both sides of that plus sign are known right now, at compile time. So the compiler just does the math, once, and your program ships with 42 baked in. That's called constant folding, and it's the tamest thing that happens down here. Code that can't be reached gets deleted. Small functions get inlined into their callers. Work gets hoisted out of loops. Your program comes out meaning exactly the same thing, arranged meaner.
The last big lowering step in Rust's pipeline explains a name you've definitely seen: LLVM. Rust translates MIR into LLVM IR, a kind of portable almost-assembly, and hands it off. LLVM is a giant shared backend: it takes that IR in, runs decades of accumulated optimization on it, and spits out machine code for whatever CPU you asked. And it's not Rust's. Clang uses it for C and C++. Swift uses it. Julia uses it. Which means they all share the same optimizer and the same codegen, and a smarter LLVM makes every one of them faster for free.
This also quietly reframes what a "compiler" is. The thing we call the Rust compiler is mostly a frontend: it understands Rust, checks Rust, lowers Rust, and then delegates the machine-specific grunt work to a backend it shares with half the industry. The tutorials' single arrow was hiding a whole supply chain.
The last mile
From there, the old four-step story picks back up. The machine code gets written into object files, holes and all, and the linker stitches your objects and your libraries into an executable the operating system knows how to load. There's more to say about what's inside that file, and it turns out to be the key to why binaries don't travel between machines, but that's part 6's job.
So that's what "compiled" means
Here's the definition I wish I'd been given: compiling is transforming a program from one representation to another, and a "compiled language" in the everyday sense is one whose toolchain runs the entire pipeline ahead of time, all the way down to machine code, before the program ever runs. You pay the wait up front, once, and ship the finished thing.
But look at the pipeline again, because here's the twist that sets up the rest of this series. Nothing forces you to ride it all the way down. Some toolchains deliberately stop at one of the middle floors, package up that half-lowered form, and ship that instead. That middle-floor format has a famous name, you've been using it for years, and your CPU cannot execute a single byte of it. That's next: bytecode is not machine code.