Rust compiler

rustc
DeveloperRust Project
Initial release2010
Stable release
1.96 / May 28, 2026 (2026-05-28)
Written inRust
Operating systemCross-platform
TypeCompiler
LicenseApache 2.0 / MIT License
Websitewww.rust-lang.org
Repositorygithub.com/rust-lang/rust

The Rust compiler, usually invoked as rustc, is the official compiler for the Rust programming language. It is developed by the Rust Project and released under a dual Apache 2.0 and MIT License.[1]

rustc translates Rust source code into native machine code. It is itself written in Rust and is self-hosting: each new version is built using the previous stable release.[2] Most developers do not call rustc directly. They use Cargo, Rust's standard build tool and package manager, which invokes the compiler with the right options for a given project.[3]

rustc is best known for two features: a type system that enforces memory and thread safety at compile time, and error messages that point at the offending source span and often suggest a fix.[1]

History

Rust began in 2006 as a personal project of Graydon Hoare, then an engineer at Mozilla. Mozilla began sponsoring the project shortly before its public announcement in 2010. The earliest compiler was written in OCaml. A version of rustc written in Rust replaced it in 2011, after which the compiler has built itself.

The first stable release, Rust 1.0, shipped on 15 May 2015. From that point on, the project committed to backward compatibility: code that compiled on a given stable release would continue to compile on later ones, and new language features were added through opt-in editions rather than breaking changes.

In August 2020, Mozilla laid off a number of staff who had been working on Rust, as part of broader layoffs at the company. The Rust Foundation was established in February 2021 to host the trademarks and infrastructure that had previously sat with Mozilla, with several large technology companies as founding members. Day-to-day work on the compiler continued under the existing volunteer-led teams.

Usage and basic invocation

rustc can compile a single Rust source file without Cargo. A minimal source file named hello.rs contains a main function:

fn main() {
    println!("Hello, world!");
}

The file can be compiled directly from a shell:

rustc hello.rs

This produces an executable named hello on Unix-like systems, or hello.exe on Windows, using the default edition and optimisation settings.[1] Direct invocation is useful for small experiments, compiler tests and build systems that call rustc themselves.

For ordinary projects, Cargo is the usual entry point. A typical workflow creates a package, builds it, runs it, and then executes its tests:

cargo new hello
cd hello
cargo build
cargo run
cargo test

Cargo stores build output under target/, resolves dependencies from crates.io, and forwards the appropriate command-line options to rustc.[3]

Editions

An edition is an opt-in revision of the Rust language that is implemented by rustc. Each crate declares which edition it targets in its manifest, and rustc applies the corresponding parsing, name-resolution and lint rules for that crate. Crates on different editions can still be linked together in the same program, which lets the ecosystem move forward without splitting in two.[4]

Four editions have been released:

  • 2015 — the baseline edition from Rust 1.0. In rustc this is effectively the compatibility mode for older crates and tools.
  • 2018 — introduced the first large migration wave, including module-system path changes and non-lexical lifetimes (NLL). rustc provides edition lints and automated suggestions to help migrate 2015 code.
  • 2021 — changed several defaults and trait-resolution edges, including array IntoIterator behaviour and closure-capture adjustments. rustc applies these changes only when a crate opts into 2021.
  • 2024 — continues the same model with narrower semantic fixes (for example temporary-scope and capture-corner cases) while preserving cross-edition compatibility through rustc's per-crate edition handling.

An edition is selected per crate in its Cargo.toml manifest:

[package]
name = "myapp"
version = "0.1.0"
edition = "2021"

When invoking rustc directly, the equivalent is the --edition 2021 flag. Omitting the field falls back to the 2015 edition for backward compatibility. For Cargo-based projects, edition migration is typically guided by rustc diagnostics and cargo fix --edition. For Cargo-based projects, edition migration is typically guided by rustc diagnostics and cargo fix --edition.[4]

Compilation process

The architecture of rustc follows a multi-stage pipeline that progressively lowers the abstraction level of the source code.[2] In simplified terms, rustc first checks what the program means, then checks whether it obeys Rust's safety rules, and only then asks a backend to generate machine code.

Frontend and intermediate representation

The compilation process begins with the frontend parsing the raw source code into an Abstract Syntax Tree (AST). During this phase, the compiler expands macros and resolves module imports. Rather than translating the AST directly to machine code, rustc converts it through multiple language-specific intermediate representations (IR). The AST is first lowered into a High-Level Intermediate Representation (HIR), where the compiler performs name resolution, type inference, and trait checking. It is then transformed into the Mid-Level Intermediate Representation (MIR). MIR is a simplified control-flow graph specifically designed to execute the language's borrow checker and run Rust-specific optimizations, such as removing dead code, before passing the program to the backend.[2]

Parsing and macro expansion

The compiler first parses the source text into an abstract syntax tree (AST). Once the AST exists, macros declared in the program are expanded. Expansion can introduce new code, which then has to be parsed in turn, so this step runs to a fixed point before the next stage begins. Module imports are resolved alongside this work.[1]

High-Level Intermediate Representation

The AST is then lowered into the High-Level Intermediate Representation, or HIR. The HIR is closer to the language's semantics than to its surface syntax: it has no macro calls, no shorthand forms, and a single canonical shape for each construct. The compiler performs name resolution, type inference and trait resolution on the HIR.[2]

Mid-Level Intermediate Representation

After type checking, rustc lowers the HIR into the Mid-Level Intermediate Representation, or MIR. MIR is a simple control-flow graph in which each basic block contains a short list of statements and ends in a single terminator. It was introduced in 2016 to give the compiler a representation that was easier to analyse than the HIR but still preserved enough information about Rust's semantics for safety checks.[5]

The MIR is the level at which the borrow checker runs. A small set of Rust-specific optimisations also runs on MIR before code generation, including constant evaluation (const eval), removal of dead code and inlining of small functions.[2]

Borrow checker

The borrow checker is the part of rustc that enforces Rust's ownership rules — strict memory safety rules checked at compile time. A value has exactly one owner at a time. References are either a single mutable borrow or any number of shared, read-only borrows, but never both at once. For every value in the program it tracks which part of the code currently owns the value, and which parts hold references ("borrows") to it. By tracking lifetimes and ownership within the MIR, the compiler prevents common memory bugs such as dangling pointers and data races without relying on a runtime garbage collector.[6] These restrictions also rule out use-after-free, double-free and most data races of the kind common in C and C++. When a program breaks the rules, the compiler refuses to build it and prints an explanation that points at the offending code.

The original borrow checker worked on lexical scopes: a borrow was treated as live until the end of the block it was declared in, which sometimes rejected code that was in fact safe. Non-lexical lifetimes (NLL), which let the compiler end a borrow as soon as it is no longer used, were stabilised in the 2018 edition.[7] A follow-up project, Polonius, reformulates the analysis as a Datalog-style set of rules and aims to accept further safe programs that the current checker still rejects. As of 2026 it remains experimental.[2]

A short example shows what the checker rejects. The following program tries to use a value after it has been moved into another binding:

fn main() {
    let s = String::from("hello");
    let t = s;          // ownership of the String moves into `t`
    println!("{}", s);  // error: `s` no longer owns the value
}

rustc rejects the program with diagnostic E0382 (borrow of moved value), pointing at the println! call and at the earlier line where ownership was transferred.[8]

Backend and code generation

After the MIR is validated and optimized, the compiler invokes a backend to generate the final object code. By default, rustc uses LLVM as its primary backend framework. The compiler translates the MIR into LLVM IR, relying on LLVM to handle aggressive architecture-specific optimizations and target-specific code generation.[1] Finally, a linker — typically the system linker, or LLD on some targets — combines the resulting object files into a single executable image. To improve compile times during development and expand the language's platform coverage, the rustc architecture supports pluggable alternative backends based on GCC and Cranelift.[2]

Incremental and parallel compilation

Internally, rustc is organised around a memoised query system. Each fact the compiler needs about the program — say, the type of an expression or the set of impls available for a given trait — is computed on demand by a query, and the results are cached on disk between runs.[2] This is what makes incremental compilation work: when a file changes, the compiler only re-runs the queries whose inputs actually changed.

The query system is implemented as a dependency graph, called the DepGraph. Every query — type_of, mir_borrowck, optimized_mir and many others — records what it read and what it produced. When a source file changes, rustc hashes the new AST/HIR fingerprint, marks stale queries and reuses cached results for unaffected parts of the crate. As a result, a second cargo build after a small change typically completes much faster than the first.

Cargo turns this on by default for dev builds: the dev profile sets incremental = true, so dependency crates and a project's own code get incremental artifacts under target/debug/incremental/ (and the query cache lives alongside it in the build directory).[3] Release builds normally skip incremental compilation because the extra bookkeeping does not pay off for a one-off optimised compile.

Parallelism is handled separately. Code generation already splits work through -C codegen-units=n — each unit is lowered to LLVM in parallel and linked at the end. The newer push is the parallel frontend on nightly: type-checking, borrow-checking and MIR opts can run with -Z threads=8 (via RUSTFLAGS or .cargo/config.toml), though it stays off by default because threading bugs still show up occasionally.[2] Parsing and macro expansion are still mostly serial as of the current compiler guide.

For incremental compilation to be reliable, queries must behave as pure functions of their inputs. If a query could depend on hidden global state, cached results could be reused incorrectly after a rebuild; this constraint shapes many internal rustc refactors.

Compiler driver and crate structure

The compiler is itself organised as a workspace of small crates in the rustc_* namespace, tied together by an entry point called rustc_driver. Each phase of the pipeline lives in roughly its own crate, which makes it easier for tools such as Clippy and rust-analyzer to reuse parts of the compiler without taking on the full pipeline.[2]

Some of the more visible crates are:

  • rustc_driver — wires the phases together and parses the command line.
  • rustc_lexer and rustc_parse — tokenise the source and produce the AST.
  • rustc_ast and rustc_expand — define AST types and run macro expansion.
  • rustc_resolve — resolves names and modules.
  • rustc_hir and rustc_hir_analysis — define the HIR and perform type and trait checking (rustc_hir_analysis replaced the earlier rustc_typeck crate).
  • rustc_borrowck — runs the borrow checker on MIR.
  • rustc_mir_build and rustc_mir_transform — build MIR from HIR and run MIR-level optimisations.
  • rustc_middle — central types and the query engine shared across phases.
  • rustc_codegen_ssa and rustc_codegen_llvm — code generation, with LLVM as the default backend.

This layout reflects the query-based design: most crates contribute query implementations rather than driving the pipeline directly.

Diagnostics

rustc is often cited for the quality of its error messages.[1] A typical diagnostic includes the span of source text that caused the error, a short explanation of what went wrong, and, where possible, a suggested fix that the user can apply directly. Errors are also given a stable identifier of the form E0000, and a longer write-up of any one of them can be obtained by running rustc --explain E0000.[8]

For the move example above, rustc reports diagnostic E0382 in a form similar to the following:

error[E0382]: borrow of moved value: `s`
 --> hello.rs:4:20
  |
2 |     let s = String::from("hello");
  |         - move occurs because `s` has type `String`,
  |           which does not implement the `Copy` trait
3 |     let t = s;
  |             - value moved here
4 |     println!("{}", s);
  |                    ^ value borrowed here after move

The exact wording can change between compiler releases, but the structure is stable: an error code, a primary location, secondary notes and a pointer to the span where compilation failed.

Command-line interface

While most users go through Cargo, rustc itself takes a large set of command-line flags.[1] Some commonly used options are:

  • --edition year — select the Rust edition (for example --edition 2021).
  • --target triple — cross-compile for another platform such as x86_64-unknown-linux-gnu or aarch64-apple-darwin.
  • --crate-type kind — choose the output kind: bin, lib, rlib, dylib, cdylib, staticlib or proc-macro.
  • --emit kinds — emit intermediate artifacts such as asm, llvm-ir, mir, obj or metadata in addition to (or instead of) a linked binary.
  • -C opt-level=N — control optimisation. Values are 0 through 3, plus s and z for small binary size.
  • -C codegen-units=N — split the crate into N codegen units that LLVM can lower in parallel; smaller numbers tend to produce faster code at the cost of compile time.
  • -C target-cpu=cpu — tune code generation for a specific CPU, with native meaning the host machine.
  • --print kind — print compiler information such as sysroot, target-list or cfg without compiling anything.
  • --explain code — print the long-form explanation for an error or lint code, for example rustc --explain E0382.

Unstable options begin with -Z and are only accepted by nightly builds.

Tooling and ecosystem

rustc is normally used through a small set of companion tools, most of which ship together as the default Rust toolchain.[3] Most Rust developers interact with the compiler indirectly through Cargo. Cargo manages project dependencies, downloads packages (known as "crates") from the official crates.io registry, and automatically invokes rustc with the correct flags and dependency paths to ensure reproducible builds. The compiler is also tightly integrated with other official tools, such as the rustfmt code formatter and the Clippy linter, which runs as a rustc driver and inspects HIR, types and MIR to catch common mistakes and unidiomatic code. Component installation, including fetching specific release channels of rustc (stable, beta, or nightly), is typically managed by rustup, the official toolchain installer.[9]

Cargo

Cargo is the build tool and package manager. It reads a project's Cargo.toml manifest, resolves and downloads dependencies from the crates.io registry, and invokes rustc with the correct flags. A cargo build command compiles the workspace; cargo test runs unit and integration tests; cargo run builds and executes a binary target. cargo publish uploads a crate to the registry after local checks pass.

Cargo manifests can also declare dependencies and build-profile settings:

[package]
name = "example"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = "1"

[profile.release]
opt-level = 3
lto = true

Cargo has named profiles such as dev, release, test and bench. Profiles control optimisation level, debug information, link-time optimisation and related compiler options. Larger projects can also use workspaces, where a top-level [workspace] table lists member crates that share one dependency lock file and build directory.[3]

rustup

rustup installs and updates toolchains, and switches between stable, beta and nightly. It can install optional components such as rustfmt and Clippy alongside rustc, add cross-compilation targets for other platforms, and pin a toolchain version for a project through a rust-toolchain.toml file.

For example, a developer can install formatting and linting components, then add a cross-compilation target:

rustup component add rustfmt clippy
rustup target add aarch64-unknown-linux-gnu
cargo build --release --target aarch64-unknown-linux-gnu

A project can pin its toolchain with rust-toolchain.toml, which rustup reads automatically when commands run inside the project directory.

rustfmt

rustfmt formats source code to a standard style defined by the Rust style guidelines. Teams use it to keep formatting consistent across contributions; cargo fmt runs it across an entire workspace.

The formatter can be configured through a rustfmt.toml file. Common settings cover line width, import grouping and whether unstable formatting options are allowed on nightly toolchains.

Clippy

Clippy is a linter shipped as a rustc driver plugin. Most of its lints use LateLintPass and inspect HIR and type information; some early lints work on the AST alone. It reports hundreds of lints grouped by severity, from probable bugs to style suggestions that the base compiler does not flag. cargo clippy runs it as part of a normal build workflow.

Lint groups include clippy::correctness, clippy::style, clippy::pedantic and clippy::nursery. A specific lint can be allowed at the crate, module or item level:

#[allow(clippy::needless_return)]
fn answer() -> i32 {
    return 42;
}

rust-analyzer

rust-analyzer is a language server used by editors and IDEs. It is a separate incremental frontend that shares selected compiler libraries with rustc (such as the lexer and type/trait-solving crates) while maintaining its own analysis pipeline, providing go-to-definition, completions, rename and inline diagnostics while source files are open.

The language server integrates with Cargo metadata, can run cargo check in the background, and uses a proc-macro server so editor features can understand code generated by procedural macros.

Release channels

rustc is released on three channels in parallel:[9]

  • Stable — a new release every six weeks. Only features marked stable are available.
  • Beta — what the next stable release will be, frozen six weeks ahead of time so that bugs can be found before promotion.
  • Nightly — built from the current development branch every day. Unstable features are gated behind explicit opt-in flags and are only available on this channel.

The recommended way to install and switch between channels is rustup, the project's official toolchain manager, which can install multiple toolchains side by side and select between them per project.

Bootstrapping

Because rustc is written in Rust, building it from source needs an existing Rust compiler. The official build process handles this in stages. A pre-built compiler binary, called stage 0, is downloaded from the project's servers. Stage 0 is used to build the current source tree, producing stage 1. Stage 1 then rebuilds the same source tree to produce stage 2, which is the compiler that is finally installed. The two-step build catches bugs in which stage 1 and stage 2 would otherwise disagree on the output.[2]

An independent project, mrustc, implements a subset of Rust in C++ that is sufficient to compile an older version of rustc. By chaining mrustc through several historical rustc releases, it is possible to build a current Rust compiler without ever downloading a binary, which addresses the trusting trust problem.[10] mrustc deliberately omits the borrow checker on the assumption that the input has already been validated by rustc.

Alternative backends

rustc's code-generation backend is pluggable. Besides LLVM, two alternative backends are available:[2]

Cranelift

Cranelift is a code generator originally written for WebAssembly runtimes. rustc uses it to speed up unoptimised (debug) builds at the cost of slower runtime performance.

The integration ships as the rustc_codegen_cranelift crate. On nightly toolchains it can be enabled via the unstable codegen-backend feature (for example -Zcodegen-backend=cranelift or a [profile.dev] entry in config.toml).[11] It uses the same MIR pipeline as the LLVM backend but trades runtime speed for faster codegen, which makes it useful during day-to-day development when developers rebuild often. The backend remains experimental and is not a replacement for LLVM in optimised release builds.

rustc_codegen_gcc

This backend emits code through GCC using its libgccjit interface. It extends Rust's hardware reach to targets that GCC supports but LLVM does not, and is part of a wider effort to give Rust a second independent code-generation path.[12]

It reuses GCC's existing optimisation and code-generation passes instead of LLVM's. That matters for embedded and mainframe platforms where GCC already has mature support. It is a separate effort from gccrs, the standalone GCC frontend for Rust: rustc_codegen_gcc keeps rustc's frontend and swaps only code generation, whereas gccrs aims to compile Rust entirely within GCC.

Alternative implementations

While rustc is the primary reference implementation, the broader Rust ecosystem includes alternative compilers designed for specific technical constraints:

mrustc

mrustc is an alternative Rust compiler written entirely in C++. It is primarily used to bootstrap the official rustc compiler from source, mitigating the trusting trust problem by removing the need to rely on pre-compiled binaries downloaded from the internet.[10] It intentionally omits the borrow checker, operating on the assumption that the code being compiled has already been validated by rustc.

gccrs

gccrs is a full frontend for the GNU Compiler Collection (GCC). Its sources were merged into GCC 13 in 2023 as an experimental, incomplete frontend; as of 2026 it still requires an explicit opt-in flag to use and cannot build the full standard library.[13] It aims to compile Rust for architectures supported by GCC, including some embedded targets where LLVM support is limited.

The Rust Project treats rustc as the language's reference implementation, and language changes are defined by what rustc accepts rather than by a separate formal specification.[1]

See also

References

  1. ^ a b c d e f g h "The rustc book". doc.rust-lang.org. Rust Project. Retrieved 5 June 2026.
  2. ^ a b c d e f g h i j k l "Rust Compiler Development Guide". rustc-dev-guide.rust-lang.org. Rust Project. Retrieved 5 June 2026.
  3. ^ a b c d e "The Cargo Book". doc.rust-lang.org. Rust Project. Retrieved 5 June 2026.
  4. ^ a b "Rust Edition Guide". doc.rust-lang.org. Rust Project. Retrieved 5 June 2026.
  5. ^ Matsakis, Niko (8 April 2016). "Introducing MIR". Rust Blog. Retrieved 5 June 2026.
  6. ^ "The Rust Reference". doc.rust-lang.org. Rust Project. Retrieved 5 June 2026.
  7. ^ "Announcing Rust 1.31 and Rust 2018 Preview". Rust Blog. 12 October 2018. Retrieved 5 June 2026.
  8. ^ a b "Error codes index". doc.rust-lang.org. Rust Project. Retrieved 5 June 2026.
  9. ^ a b "The rustup book". rust-lang.github.io. Rust Project. Retrieved 5 June 2026.
  10. ^ a b "mrustc". GitHub. Retrieved 5 June 2026.
  11. ^ "rustc_codegen_cranelift". GitHub. Rust Project. Retrieved 5 June 2026.
  12. ^ "rustc_codegen_gcc". GitHub. Rust Project. Retrieved 5 June 2026.
  13. ^ "GCC Rust front-end". gcc.gnu.org. GNU Compiler Collection. Retrieved 5 June 2026.

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.