Compiler Architecture

Purpose and Scope

This page gives contributors and advanced users a source-grounded map of the Rust compiler architecture as represented by major internal crates. The goal is not to replace the rustc-dev-guide, which the compiler crate READMEs repeatedly point to, but to explain how the named crates in this repository fit together in a compilation pipeline. Rustc is organized as a collection of crates rather than one monolithic implementation, and each crate tends to own a particular level of representation, orchestration, or analysis. Understanding those boundaries is the first practical step before reading implementation code or changing compiler behavior.

The central architectural pattern is a staged lowering of source text into increasingly analysis-friendly forms, with the driver coordinating phases and the query system connecting demand-driven computations. The rustc_ast crate is concerned with syntax: abstract syntax trees, tokens, token streams, AST mutation support, and shared definitions for lexer and macro-expansion-adjacent components. Later phases operate on more semantic representations, perform type checking, manage compiler-wide state, and enforce ownership rules. The README files in these crates are short, but they state important ownership boundaries that help prevent readers from looking for parser code in type checking crates or orchestration logic in representation crates.

Sources: compiler/rustc_driver_impl/README.md, compiler/rustc_ast/README.md, compiler/rustc_hir_analysis/README.md, compiler/rustc_middle/README.md, compiler/rustc_borrowck/src/lib.rs, compiler/rustc_query_impl/src/README.md

Relevant Source Files

  • compiler/rustc_driver_impl/README.md - Identifies the driver crate as effectively the compiler's main function and describes it as the component that orchestrates compilation and knits together other rustc crates.
  • compiler/rustc_ast/README.md - Defines the syntax-oriented crate boundary for ASTs, tokens, token streams, AST mutation infrastructure, and shared definitions used by lexer and macro expansion code.
  • compiler/rustc_hir_analysis/README.md - Points readers to the high-level type checking documentation for HIR-based analysis, marking this area as the semantic analysis layer rather than a syntax layer.
  • compiler/rustc_middle/README.md - Serves as the crate-level entry point for compiler middle infrastructure and directs readers toward the broader rustc-dev-guide for how rustc works.
  • compiler/rustc_borrowck/src/lib.rs - Anchors the borrow checking crate entry point, the ownership and borrowing enforcement phase that contributors usually encounter after type and MIR-oriented lowering work.
  • compiler/rustc_query_impl/src/README.md - Identifies the query implementation area and points to the rustc-dev-guide chapter on the compiler query system.

System-to-Code Mapping

The driver is the best starting point because it explains the shape of the whole executable. Its README says the driver crate is effectively the main function for the Rust compiler. That phrasing matters: the driver is not described as the home of the compiler's main logic, but as the place that orchestrates compilation and knits together logic from the other crates. It may contain support for pretty printing and minor compiler options, yet its primary architectural responsibility is coordination. When tracing a compile from command invocation to diagnostics, begin with the driver mindset: it schedules and connects subsystems rather than owning every transformation.

Syntax lives in rustc_ast, and the README draws a deliberately narrow boundary around syntax-level data. The crate contains the abstract syntax tree, token and token-stream definitions, traits and data structures for mutating ASTs, and shared definitions for AST-related compiler components such as the lexer and macro expansion. That means contributors should think of this layer as close to parsed input and macro-expanded structure, not as the place where type relationships or borrow rules are decided. The AST layer is where source form is represented before later compiler phases attach richer semantic meaning.

The HIR analysis crate is represented in the evidence by a README that points to high-level documentation for HIR type checking. Even from that compact signal, the architectural role is clear: after parsing and syntactic processing, rustc performs semantic analysis over a high-level intermediate representation. Type checking is the phase where Rust's type rules are applied to program structure, so this crate sits beyond tokens and ASTs and before later checks that depend on typed program information. The term HIR, or high-level intermediate representation, names a compiler representation designed to be more convenient for analysis than raw syntax.

rustc_middle is less specifically described in the supplied snippet, but its README functions as an architectural signpost to the broader explanation of how rustc works. In rustc terminology, middle-layer crates usually hold shared representations, context, and infrastructure used by several analysis and transformation phases. For a contributor, the useful lesson is that not every concept belongs in the phase-specific crate where it is first observed. Shared compiler state and cross-phase data tend to live in middle infrastructure so driver orchestration, queries, analysis passes, and later phases can communicate through stable internal abstractions.

Sources: compiler/rustc_driver_impl/README.md, compiler/rustc_ast/README.md, compiler/rustc_hir_analysis/README.md, compiler/rustc_middle/README.md

Architectural concernSource anchorPractical reading question
Orchestrationcompiler/rustc_driver_impl/README.mdWhich component starts and coordinates compilation?
Syntax representationcompiler/rustc_ast/README.mdWhere are ASTs, tokens, token streams, and AST mutation support defined?
HIR type analysiscompiler/rustc_hir_analysis/README.mdWhere should a reader look for high-level type checking concepts?
Shared middle infrastructurecompiler/rustc_middle/README.mdWhich layer is a signpost for compiler-wide internal structures?
Borrow checkingcompiler/rustc_borrowck/src/lib.rsWhere is the ownership and borrowing enforcement crate rooted?
Demand-driven computationcompiler/rustc_query_impl/src/README.mdWhere is the query-system implementation area introduced?

Compilation Pipeline Walkthrough

A practical way to read rustc is to follow the data rather than alphabetically browsing crates. The driver receives the compilation request and coordinates the rest of the system. Early work turns input into syntax-level structures owned by rustc_ast, including tokens, token streams, and the AST. That syntax layer is close to what the programmer wrote, although it also supports mutation and shared AST-related definitions needed by components such as macro expansion. At this point, the compiler is still largely concerned with form: what items, expressions, tokens, and syntactic constructs appear in the crate being compiled.

The next conceptual move is from syntax to analysis-oriented representations. HIR type checking is the named doorway in the supplied source evidence: rustc_hir_analysis directs readers to the HIR type checking chapter of the rustc-dev-guide. HIR exists so the compiler can reason about Rust programs at a higher semantic level than raw parsed syntax. Type checking determines whether expressions, items, and inferred relationships satisfy Rust's type rules. This phase needs information that syntax alone does not encode, so it depends on earlier lowering and shared compiler context rather than merely walking token streams.

Borrow checking is another major semantic checkpoint. The requested source path compiler/rustc_borrowck/src/lib.rs anchors the crate that enforces Rust's ownership and borrowing model inside the compiler. From a pipeline perspective, borrow checking should be understood as a later analysis that relies on the compiler already having enough semantic structure to ask questions about moves, references, lifetimes, and access permissions. Contributors often meet this area when a language change affects when values may be used, when references are valid, or how diagnostics explain ownership errors.

The query implementation connects these stages by making compiler computations demand-driven and reusable. The query README points readers to the rustc-dev-guide's query-system chapter, which is a strong signal that this crate is infrastructure rather than a single language feature. A query system lets compiler phases ask for computed facts, cache answers, and express dependencies between computations. That architectural choice is essential in a compiler as large as rustc: type checking, borrow checking, diagnostics, and downstream phases need consistent access to shared facts without every pass manually recomputing or threading all data through the driver.

Sources: compiler/rustc_driver_impl/README.md, compiler/rustc_ast/README.md, compiler/rustc_hir_analysis/README.md, compiler/rustc_borrowck/src/lib.rs, compiler/rustc_query_impl/src/README.md

API Components and Internal Contracts

The public contract of these crates is internal to the compiler workspace, but the READMEs still describe stable contributor-facing roles. The driver contract is orchestration: expect it to invoke, configure, or sequence other compiler components, and do not assume it owns the deep rules of parsing, type checking, or borrow checking. The AST contract is syntax ownership: expect AST definitions, token structures, token streams, AST mutation traits, and shared syntax definitions. The HIR analysis contract is semantic checking over lowered program structure, especially type checking. The query implementation contract is infrastructure for computing compiler facts on demand.

These boundaries are useful when evaluating a change. If a feature introduces a new syntactic form, the first architectural question is whether it needs AST, token, parser, or macro-expansion support, making rustc_ast part of the initial map. If a change modifies type inference, trait obligations, or semantic validation, HIR analysis is more likely to be involved. If it changes ownership diagnostics or the rules around use after move and borrowing, the borrow checker crate becomes relevant. If the change adds or reorganizes compiler-wide computed information, the query implementation and middle-layer abstractions become part of the design discussion.

The architecture also discourages placing convenience logic wherever it is easiest to reach. Because the driver is explicitly described as not containing the main compiler logic, changes there should remain focused on coordination, options, and presentation-level behavior. Because rustc_ast is syntax-oriented, it should not become a dumping ground for later semantic concepts. Because rustc_middle serves as a broad middle-layer signpost, contributors should be careful to distinguish genuinely shared compiler abstractions from phase-local details. These separations keep the compiler navigable and reduce circular dependencies between representation, analysis, and orchestration layers.

Sources: compiler/rustc_driver_impl/README.md, compiler/rustc_ast/README.md, compiler/rustc_hir_analysis/README.md, compiler/rustc_middle/README.md, compiler/rustc_query_impl/src/README.md

Contributor Reading Strategy

When approaching compiler work, start by naming the phase affected by the behavior you want to change. For command-line flow, session setup, or the top-level compile process, read from the driver outward. For grammar-facing or macro-facing work, start with the syntax layer and then follow references into parser or expansion documentation. For type-system behavior, begin with HIR type checking before reading lower-level utilities. For ownership and borrowing behavior, start from the borrow checker crate and then identify which earlier representations and queries provide its inputs. This phase-first strategy keeps investigation bounded and avoids treating rustc as a flat codebase.

The short crate READMEs repeatedly point to the rustc-dev-guide, and that is an intentional part of the repository's documentation architecture. Use this OpenWiki page to identify the right crate boundary, then use the dev guide links from the corresponding README to learn the detailed algorithms and invariants. After that, return to the source files with a specific question: which representation is being transformed, which query is being requested or provided, and which phase owns the rule being changed? That loop between crate boundary, guide chapter, and source file is the most efficient path for first-time contributors.

A final practical next step is to pair architecture reading with tests and diagnostics relevant to the phase. Syntax changes usually require parser, macro expansion, or UI tests. Type checking and borrow checking changes often require diagnostics-oriented tests because error quality is part of rustc's user-facing behavior. Query and middle-layer changes require extra care because they can affect many phases indirectly. Before editing, write down whether your change is orchestration, representation, semantic analysis, ownership enforcement, or shared computation; then start in the source path that owns that concern rather than in the first file mentioned by a failing test.

Sources: compiler/rustc_driver_impl/README.md, compiler/rustc_ast/README.md, compiler/rustc_hir_analysis/README.md, compiler/rustc_middle/README.md, compiler/rustc_borrowck/src/lib.rs, compiler/rustc_query_impl/src/README.md

Read rustc-overview next if you want the compiler executable and driver role in a broader user-facing context. Read rustc-command-line if the behavior you are investigating begins with compiler options rather than language semantics. Read codegen-backends after you understand front-end analysis and want to follow the pipeline toward machine-code generation. Read building-rust-from-source before making local compiler changes, because rustc architecture only becomes actionable once you can build and test the repository. Read testing-and-ci when you are ready to validate a change against the repository's compiletest, codegen, crash, and tool test organization.