rgoussu@goussu: ~/library/rust/toolchain
~/library/rust/toolchain cat rustc.md

rustc — the compiler behind cargo

# The compilation pipeline — HIR/MIR, borrow checking, monomorphization, LLVM codegen — and the knobs that matter: codegen units, incremental builds, lints, error codes.

Conceptsaved 2026-08-09 #rust#tooling#rustc#compilers

Overview

rustc is the compiler nobody invokes directly — cargo drives it — but whose pipeline explains both Rust's famous error messages and its famous compile times. Source is lowered through HIR (post-macro-expansion AST) to MIR (a control-flow-graph IR), where borrow checking and drop elaboration run, then monomorphized generics feed LLVM for optimization and codegen. Knowing roughly where each phase's cost and each error's origin lies is what turns "the compiler is slow/angry" into an actionable model.

Key points

  • The pipeline: parse → macro expansion → HIR (type checking, trait resolution) → MIR (borrow check, drop elaboration, const eval) → monomorphization → LLVM IR → object code. The borrow checker is a MIR pass — exactly the analysis miri later interprets.

    flowchart TB
        SRC[Source] --> PA[Parse]
        PA --> ME[Macro expansion]
        ME --> HIR["HIR: type checking, trait resolution"]
        HIR --> MIR["MIR: borrow check, drop elaboration, const eval"]
        MIR --> MO["Monomorphization: one copy per concrete instantiation, the compile-time and binary-size cost"]
        MO --> IR["LLVM IR: optimized in parallel codegen units"]
        IR --> OBJ[Object code]
    
  • Monomorphization is the cost model: every concrete instantiation of a generic function gets its own copy — zero-cost dispatch at runtime, paid for in compile time and binary size; dyn Trait is the escape valve.

  • Codegen units: each crate is split into parallel LLVM units (16 in release by default); fewer units = better optimization, slower builds — the codegen-units = 1 + LTO release recipe lives in profiles.

  • Incremental compilation caches per-query results in target/incremental — on by default for dev, off for release; the reason the second cargo check is fast.

  • Error codes are documentation: every E0502 has a full explanation via rustc --explain E0502 — the error-message culture is a deliberate, resourced project value.

  • Lint levels are compiler-native: #[allow]/#[warn]/#[deny] per item, crate or command line; clippy plugs its lints into this same machinery.

  • RUSTFLAGS and -C flags: -C target-cpu=native, -C opt-level, -C debuginfo — usually reached through cargo profiles, not directly.

  • No stable ABI: rlibs and Rust dylibs only link against artifacts from the same compiler version — the deep reason cargo rebuilds the world per toolchain and artifact formats look the way they do.

Examples

rustc --explain E0382                # use of moved value, chapter and verse
cargo build --timings                # HTML report: which crate dominates the build
RUSTFLAGS="-C target-cpu=native" cargo build --release
cargo rustc -- -Zself-profile        # (nightly) per-pass compiler profiling

Related