rustc — The Rust Compiler
rustc is the official compiler for the Rust programming language. It takes Rust source files (.rs), performs syntax parsing, macro expansion, type checking, and borrow checking, and uses LLVM to generate native machine code executables or libraries.
Invoking rustc Directly
While multi-file projects use cargo to orchestrate builds, rustc can compile standalone .rs files directly from the command line.
Given a source file named hello.rs:
fn main() {
println!("Compiled directly with rustc!");
}
Compile and execute it:
rustc hello.rs
./hello
Compiled directly with rustc!
By default, rustc reads the input file hello.rs and outputs a native executable binary named after the source stem (hello on macOS and Linux, hello.exe on Windows) in the current working directory.
Common rustc Command-Line Flags
| Flag | Example | Description |
|---|---|---|
-o <filename> |
rustc main.rs -o app |
Writes the compiled output to <filename> instead of the source file's stem |
--edition <year> |
rustc --edition 2024 main.rs |
Specifies the Rust edition (2015, 2018, 2021, or 2024; defaults to 2015 if omitted!) |
-O |
rustc -O main.rs |
Enables optimizations (equivalent to -C opt-level=2) |
-C opt-level=<val> |
rustc -C opt-level=3 main.rs |
Sets optimization level (0, 1, 2, 3, s for size, or z for minimum size) |
-g |
rustc -g main.rs |
Includes debug info in the binary (equivalent to -C debuginfo=2) |
--crate-type <type> |
rustc --crate-type lib math.rs |
Builds a library or specific artifact instead of an executable binary |
--emit <kind> |
rustc --emit asm main.rs |
Controls output format (asm, llvm-ir, llvm-bc, obj, metadata, link, dep-info, mir) |
--explain <code> |
rustc --explain E0382 |
Prints a detailed explanation and example for a compiler error code |
-W / -D / -A <lint> |
rustc -D warnings main.rs |
Sets lint level: Warn (-W), Deny/error (-D), or Allow/silence (-A) |
--target <triple> |
rustc --target wasm32-unknown-unknown |
Cross-compiles code for a specific target architecture and OS |
-V / --version |
rustc --version --verbose |
Prints compiler version, commit hash, host triple, and LLVM version |
Specifying the Rust Edition (--edition)
When cargo calls rustc, it passes --edition 2024 based on Cargo.toml. However, for strict backward compatibility with scripts written in 2015, invoking rustc directly on the command line without --edition defaults to the 2015 edition. Always pass --edition 2024 when compiling modern standalone files with rustc:
rustc --edition 2024 hello.rs
Crate Types Produced by rustc
In Rust, the unit of compilation is a crate—not an individual file. Even if a crate spans dozens of mod files, rustc compiles the root file (such as main.rs or lib.rs) and pulls in all referenced modules as a single compilation unit.
Using --crate-type, rustc can produce different output artifacts:
--crate-type |
Output File Pattern (macOS / Linux) | Purpose |
|---|---|---|
bin (default) |
<name> |
Runnable executable binary (requires a fn main() entry point) |
lib / rlib |
lib<name>.rlib |
Rust static library format containing compiled code and type/generic metadata for linking into other Rust crates |
dylib |
lib<name>.dylib / .so |
Rust dynamic library for dynamic linking between Rust crates |
staticlib |
lib<name>.a |
C-compatible static archive for linking Rust code into C or C++ programs |
cdylib |
lib<name>.dylib / .so |
C-compatible dynamic shared library for loading into C, Python, Ruby, or Node.js |
proc-macro |
lib<name>.dylib / .so |
Compiler plugin crate defining custom procedural macros (#[derive(...)]) |
Compiling and Linking a Library Crate Manually
Create a library file math.rs (note that it has no main function):
pub fn cube(n: i32) -> i32 {
n * n * n
}
Compile it as a Rust library (rlib):
rustc --edition 2024 --crate-type=lib math.rs
ls -l libmath.rlib
-rw-r--r-- 1 user staff 10848 Oct 21 12:00 libmath.rlib
Now create app.rs that calls math::cube, and link it against libmath.rlib using the --extern flag:
fn main() {
println!("4 cubed is {}", math::cube(4));
}
rustc --edition 2024 app.rs --extern math=libmath.rlib
./app
4 cubed is 64
In day-to-day development, cargo constructs these --crate-type and --extern flags automatically for every dependency in your project.
Inspecting Intermediate Representations (--emit)
As rustc compiles a source file, it lowers the code through a sequence of intermediate representations before producing machine code:
Source (.rs)
|
v
AST (Abstract Syntax Tree - parsing & macro expansion)
|
v
HIR (High-Level IR - desugaring `for`/`async`, type inference, trait resolution)
|
v
MIR (Mid-Level IR - control-flow graph, borrow checking, move/drop insertion)
|
v
LLVM IR (Backend representation - target-independent & target-specific optimizations)
|
v
Assembly (.s) / Object File (.o) ---> System Linker (cc/ld) ---> Native Executable
You can inspect these stages directly using --emit:
# Emit human-readable assembly (`.s`), LLVM IR (`.ll`), and Mid-Level IR (`.mir`)
rustc --edition 2024 -O --emit asm,llvm-ir,mir math.rs --crate-type lib
| Flag | Generated File | What It Shows |
|---|---|---|
--emit mir |
math.mir |
Rust's control-flow graph showing exact storage live/dead markers and drop calls |
--emit llvm-ir |
math.ll |
Typed LLVM instructions sent to the LLVM optimizer |
--emit asm |
math.s |
Native CPU assembly instructions (arm64 or x86_64) |
--emit obj |
math.o |
Unlinked native machine object file |
Codegen Options (-C)
The -C (or --codegen) flag passes options directly to the code generation and linking phases. Run rustc -C help to list all codegen flags:
| Codegen Option | Example | Effect |
|---|---|---|
opt-level=<0..3,s,z> |
-C opt-level=3 |
Controls LLVM optimization aggressiveness (0 = none, 3 = maximum speed, z = smallest binary) |
debuginfo=<0..2> |
-C debuginfo=2 |
Embeds DWARF/PDB source line and variable symbol tables for lldb / gdb |
target-cpu=<cpu> |
-C target-cpu=native |
Generates instructions tuned for the host machine's exact CPU microarchitecture (such as AVX2 or Apple M-series NEON) |
lto=<val> |
-C lto=fat |
Enables Link-Time Optimization across all crates in the binary |
panic=<strategy> |
-C panic=abort |
Aborts the process immediately on panic instead of unwinding the stack (reduces binary size) |
strip=<val> |
-C strip=symbols |
Strips symbol tables and debug info from the final executable |
Looking Up Error Codes with --explain
Every compile-time error in rustc with an E0xxx number has built-in offline documentation explaining the rule and showing how to fix it:
rustc --explain E0384
This error occurs when an attempt is made to reassign an immutable variable.
Erroneous code example:
```
fn main() {
let x = 3;
x = 5; // error, reassignment of immutable variable
}
```
By default, variables in Rust are immutable. To fix this error, add the
keyword `mut` after the keyword `let` when declaring the variable...
Passing --error-format=json outputs compiler diagnostics as structured JSON objects, which is how IDEs and rust-analyzer highlight errors inline in your editor.