Skip to content

Compilers

Most of what turns your .rs file into a binary was not written for Rust, and is not maintained by anyone on the Rust team.

That is the most useful fact in this section, and it explains why a compiler diagram is so crowded. Draw one and you get forty names — Clang, LLVM IR, LLD, RISC-V, CMake, LLDB, ART, GCC, MSVC — with no indication of which are rivals, which are stages of one program, and which are not compilers at all. They sort into five boxes.

Box Names in it What they have in common
Front ends rustc, Clang, GCC, MSVC, swiftc One per language. Each parses its own syntax and enforces its own rules — Rust's borrow checker is here, and only here
Stages inside one parser, optimizer, generator Not separate programs. Phases every front end runs, in that order
The shared middle LLVM, LLVM IR, Cranelift Where the languages meet. rustc, Clang and swiftc hand off to the same optimizer and code generator
Targets x86, x86-64, ARM, RISC-V The instruction set at the far end. One compiler, many of these
Standing nearby GNU ld, LLD, mold · CMake, Cargo · LLDB, GDB · JVM, ART A linker, a build system, a debugger, a runtime. None of them compiles anything, and every one is routinely called "the compiler" by somebody debugging one

The three stages, end to end

rustc parses your source, type-checks it, borrow-checks it, and lowers it to LLVM IR — a typed, machine-independent assembly language. LLVM optimizes that IR and generates machine code for one target, emitting an object file per unit. A linkerld64, GNU ld, lld — stitches those object files together with the libraries they name and produces one executable. Cargo drives all of it and compiles nothing itself.

flowchart LR
    SRC[".rs source"] --> RUSTC["rustc<br/>parse · type-check · borrow-check"]
    RUSTC --> IR["LLVM IR"]
    IR --> OPT["LLVM optimizer"]
    OPT --> GEN["LLVM generator"]
    GEN --> OBJ["object files"]
    OBJ --> LD["linker"]
    LD --> BIN["executable"]

Rust's distinctive part is the first box and nothing after it. Ownership, lifetimes, exhaustive match, trait coherence: all settled before LLVM sees anything. What comes out the far end is the same machine code a C++ program of the same shape would get, from the same optimizer, through the same linker — which is why "as fast as C" is a claim about the middle of this pipeline, where the two languages are sharing an implementation rather than achieving similar results separately.

Two consequences worth carrying:

  • A "compiler error" is at least four different things. A parse error, a type error, a borrow error and a link error come from different stages, and the last one is not from rustc at all. Knowing which stage spoke tells you which model to fix.
  • What is slow is not what is complicated. The borrow checker is the famous part and rarely the expensive one; codegen and linking usually dominate a build. Compile times is the measurement.

The lessons

Lesson Level What it teaches
What a compiler does before your program runs 101 The compile-time/run-time line, made visible: a loop that runs during the build, an array length that proves it, and the two errors that live either side of the boundary
What the optimizer does 201 Ten numbers summed in a loop compile to mov eax, 55 — the same experiment the talk runs in C++, run in Rust, with both ends of the assembly quoted
LLVM: the part of rustc that is not Rust 201 What the name actually refers to — a suite, a library, an IR and a pipeline — plus Clang, LLD and LLDB, real IR for a small function, and the control-flow graph read straight off it
The linker: the stage that is not rustc 201 Two functions this crate never defines, called anyway — what an object file leaves blank, who fills it in, and why the error text is in a different dialect from every other error you have seen
Control-flow flattening 201 → 301 The optimizer's permission aimed backwards: the same function flattened into a state machine, an opaque predicate that always holds, and the three layers where a pass can be inserted
When the type checker is wrong 301 Soundness is a property of the checker, not of your code — the thirty programs rustc accepted between 2022 and 2025 and should not have, eleven of them reachable memory bugs, and the one of the thirty short enough to put on a page and run
Makefiles: a build graph you write by hand 201 A rule, a TAB and a timestamp comparison are the whole of make — so a header nobody listed leaves a stale binary and no error, until the compiler writes the graph, which is what rustc does for every crate

Stubs

Outlines with no runnable example behind them yet — the same arrangement as Errors and Data, and each marked at the top of its page.

Lesson Level What it will teach
Targets and triples 201 x86_64-unknown-linux-gnu is four decisions in a hyphenated string — and rustup target add gives you two of the three things a cross-build needs
Compiled, interpreted, or something between 101 Where the JVM, ART, CPython and Rust actually differ — and why "compiled" describes a moment, not a language
Reading a compilation failure 101 → 201 Which stage is talking, what an E0xxx code buys you, and why a borrow error and a type error need different habits
A build system is not a compiler 201 What CMake, Make and Cargo actually do — decide what to compile and in what order — and why "why did it rebuild everything?" is never a compiler question

Where this sits, and where it came from

Tooling is the loop you sit inside — edit, build, run — and Compile times is that loop's stopwatch, phase by phase. This section is the altitude below: what each phase is, and who else is using the same machinery. Unix is the shell you run all of it from.

The framing, the map and two of the experiments come from Laurie Kirk's Thinking Like a Compiler: Obfuscation from the Other Side (RE//verse 2026), which arrives at the pipeline from the far end — a binary on a disassembler's screen, and the question of what the compiler must have done to it. It is worth an hour, and the Rust half of every experiment in it is on the pages above.

Po polsku

Po polsku „kompilacja” potocznie znaczy wszystko: kompiluje mi się projekt obejmuje cargo, rustc, LLVM i konsolidator (linker) naraz. Ta strona rozbija to na pięć pudełek i warto zapamiętać, które z nich należy do Rusta: tylko pierwsze. rustc to front-end — analiza składniowa, kontrola typów, borrow checker — i wszystko, co w Ruscie charakterystyczne (własność, czasy życia, wyczerpujący match, spójność cech — trait coherence), rozstrzyga się właśnie tam, zanim LLVM cokolwiek zobaczy. Dalej idzie wspólny środek, potem konkretna architektura docelowa, a obok stoją programy, które nie kompilują niczego, choć wszyscy nazywają je kompilatorem: konsolidator, debuger, system budowania. cargo nie kompiluje ani jednej linii — decyduje, co i w jakiej kolejności skompilować.

Kto miał na studiach kurs kompilatorów (u nas zwykle z „smoczej książki” Aho, Sethiego i Ullmana), ma już gotowe pojęcie na to, co jest w środku: kod pośredni. LLVM IR to dokładnie on, tyle że wyjęty z jednego kompilatora i zrobiony z niego produkt — rustc, clang i swiftc oddają swoją robotę temu samemu optymalizatorowi i temu samemu generatorowi kodu. Dlatego zdanie „Rust jest szybki jak C” dotyczy środka tego rurociągu i jest słabsze, niż brzmi: to nie są dwa zespoły, które niezależnie doszły do podobnego wyniku, tylko dwa języki korzystające z jednej implementacji.

Praktyczny wniosek jest terminologiczny. „Błąd kompilacji” to co najmniej cztery różne rzeczy — błąd składni, błąd typów, błąd pożyczania i błąd konsolidacji — a ten ostatni nie pochodzi od rustc i dlatego brzmi obco: mówi do ciebie ld, innym dialektem i bez kodu E0xxx. Uwaga na pułapkę wyszukiwania: konsolidator to poprawne polskie słowo, ale odpowiedzi na twój problem leżą pod undefined reference i linker error. I jeszcze jedno, wbrew intuicji: to, co słynne, nie jest tym, co wolne — borrow checker rzadko dominuje w czasie budowania, robią to generowanie kodu i konsolidacja.

Szukaj po polsku: kompilator a konsolidator · kod pośredni · analiza składniowa · rust linker error undefined reference · rustc emit llvm-ir · cargo build vs rustc