Frontend Labs
⋯

2. The toolchain: cargo, crates, editions, and what cargo build really does

2.1 The commands you will type every day

Command What it does
cargo new app / cargo new --lib mylib create a binary or library crate
cargo build / cargo build --release compile to target/debug/ or target/release/
cargo run -- args build and run; arguments after -- go to your program
cargo check type-check and borrow-check without generating code; the fastest feedback loop
cargo test build and run unit, integration, and doc tests
cargo clippy lints, about 800 of them; treat its advice as code review
cargo fmt format; like gofmt, nobody argues about style
cargo doc --open build HTML docs for your crate and every dependency
cargo add serde --features derive add a dependency to Cargo.toml
cargo tree show the dependency graph
cargo expand (plugin) show code after macro expansion

2.2 What cargo build actually does

The C guide §2.1 splits compilation into preprocess → compile → assemble → link. Rust's pipeline has different stages, and the borrow checker sits in the middle:

 source (.rs)
   │  parse, expand macros            (no textual preprocessor; macros work on tokens)
   ▼
 AST ──▶ HIR                          (name resolution, type inference, trait resolution)
   ▼
 MIR                                   (the control-flow graph that the BORROW CHECKER runs on)
   │  borrow check, const eval, MIR optimizations
   ▼
 LLVM IR  ──▶  LLVM optimizer  ──▶  object files (.o)   ← one per "codegen unit"
   ▼
 linker (cc / ld / lld)  ──▶  executable, statically linked with Rust deps, dynamically with libc

Consequences you will notice:

  • The unit of compilation is the crate, not the file. A crate is a library or binary built from a tree of modules. That is why cargo check type-checks your whole crate at once.
  • Generics are compiled per concrete type (monomorphization), so generic-heavy code is fast but compiles slowly and produces bigger binaries.
  • Rust dependencies are linked statically into your binary. ldd target/release/app on Linux shows only libc and friends.
cargo rustc --release -- --emit=asm      # see the assembly (target/release/deps/*.s)
cargo rustc -- -Zunpretty=mir            # MIR needs nightly; use https://play.rust-lang.org's "MIR" button

2.3 Cargo.toml, Cargo.lock, and editions

[package]
name = "hello"
version = "0.1.0"
edition = "2024"     # language edition: 2015, 2018, 2021, 2024

[dependencies]
serde = { version = "1", features = ["derive"] }   # "1" means ^1.0.0: any 1.x >= 1.0.0

[profile.release]
lto = "thin"         # link-time optimization across crates
codegen-units = 1    # slower build, faster binary
panic = "abort"      # smaller binary; no unwinding (see Ch 10.5)
  • Cargo.lock records the exact version of every transitive dependency. Commit it for applications and for libraries (current Cargo guidance). It does for Rust what go.sum does for Go, and it is the input to cargo audit (Ch 55).
  • Editions are opt-in language versions. They change syntax details, not the ABI. Crates from different editions link together freely, so the ecosystem never splits the way Python 2/3 did. Edition 2024 shipped in Rust 1.85 (February 2025).

2.4 Cross-compilation

rustup target add x86_64-unknown-linux-musl     # fully static Linux binaries
cargo build --release --target x86_64-unknown-linux-musl
rustup target add aarch64-unknown-linux-gnu     # needs a cross linker; see Ch 66 for `cross`

Compare Go §2.3 (GOOS=linux GOARCH=arm64 go build simply works) and the C guide §41.1 (you need a full cross toolchain). Rust sits between them: the compiler can target anything LLVM supports, but you still need a linker and a libc for the target. Ch 66 covers cross and cargo-zigbuild, which solve this.


Generated from real-life-rust-guide.md · 8 Oct 2026 16:36