rust Documentation

Empowering everyone to build reliable and efficient software.

120,623 stars Rust Rust 1 file · ~137,522 tokens #compiler#language#rust
RAW Doc

Technical Documentation: rust-lang/rust

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (README.md)

This is the main source code repository for Rust. It contains the compiler,
standard library, and documentation.

Why Rust?

  • Performance: Fast and memory-efficient, suitable for critical services, embedded devices, and easily integrated with other languages.

  • Reliability: Our rich type system and ownership model ensure memory and thread safety, reducing bugs at compile-time.

  • Productivity: Comprehensive documentation, a compiler committed to providing great diagnostics, and advanced tooling including package manager and build tool (Cargo), auto-formatter (rustfmt), linter (Clippy) and editor support (rust-analyzer).

Quick Start

Read "Installation" from The Book.

Installing from Source

If you really want to install from source (though this is not recommended), see
INSTALL.md.

Getting Help

See https://www.rust-lang.org/community for a list of chat platforms and forums.

Contributing

See CONTRIBUTING.md.

For a detailed explanation of the compiler's architecture and how to begin contributing, see the rustc-dev-guide.

License

Rust is primarily distributed under the terms of both the MIT license and the
Apache License (Version 2.0), with portions covered by various BSD-like
licenses.

See LICENSE-APACHE, LICENSE-MIT, and
COPYRIGHT for details.

Trademark

The Rust Foundation owns and protects the Rust and Cargo
trademarks and logos (the "Rust Trademarks").

If you want to use these names or brands, please read the
Rust language trademark policy.

Third-party logos may be subject to third-party copyrights and trademarks. See
Licenses for details.


Src Doc Index (`src/doc/index.md`)

% Rust Documentation

Welcome to an overview of the documentation provided by the Rust
project
. This page contains links to various helpful references,
most of which are available offline (if opened with rustup doc). Many of these
resources take the form of "books"; we collectively call these "The Rust
Bookshelf." Some are large, some are small.

All of these books are managed by the Rust Organization, but other unofficial
documentation resources are included here as well!

If you're just looking for the standard library reference, here it is:
Rust API documentation

Learning Rust

If you'd like to learn Rust, this is the section for you! All of these resources
assume that you have programmed before, but not in any specific language:

The Rust Programming Language

Affectionately nicknamed "the book," The Rust Programming Language
will give you an overview of the language from first principles. You'll build a
few projects along the way, and by the end, you'll have a solid grasp of how to
use the language.

Rust By Example

If reading multiple hundreds of pages about a language isn't your style, then
Rust By Example has you covered. RBE shows off a
bunch of code without using a lot of words. It also includes exercises!

Rustlings

Rustlings guides you
through downloading and setting up the Rust toolchain, then provides an
interactive tool that teaches you how to solve coding challenges in Rust.

Rust Playground

The Rust Playground is a great place
to try out and share small bits of code, or experiment with some of the most
popular crates.

Using Rust

Once you've gotten familiar with the language, these resources can help you put
it to work.

The Standard Library

Rust's standard library has extensive API documentation, with
explanations of how to use various things, as well as example code for
accomplishing various tasks. Code examples have a "Run" button on hover that
opens the sample in the playground.

text
Search

Your Personal Documentation

Whenever you are working in a crate, cargo doc --open will generate
documentation for your project and all its dependencies in their correct
version, and open it in your browser. Add the flag --document-private-items to
also show items not marked pub.

Rust Version History

The Release Notes describes the change history of the Rust
toolchain and language.

The Edition Guide describes the Rust editions and
their differences. The latest version of the toolchain supports all
historical editions.

The `rustc` Book

The rustc Book describes the Rust compiler, rustc.

The Cargo Book

The Cargo Book is a guide to Cargo, Rust's build tool and
dependency manager.

The Rustdoc Book

The Rustdoc Book describes our documentation tool, rustdoc.

The Clippy Book

The Clippy Book describes our static analyzer, Clippy.

Extended Error Listing

Many of Rust's errors come with error codes, and you can request extended
diagnostics from the compiler on those errors (with rustc --explain). You can
also read them here if you prefer: rustc error codes

Mastering Rust

Once you're quite familiar with the language, you may find these advanced
resources useful.

The Reference

The Reference is not a formal spec, but is more detailed
and comprehensive than the book.

The Style Guide

The Rust Style Guide describes the standard formatting
of Rust code. Most developers use cargo fmt to invoke rustfmt and format the
code automatically (the result matches this style guide).

The Rustonomicon

The Rustonomicon is your guidebook to the dark arts of
unsafe Rust. It's also sometimes called "the 'nomicon."

The Unstable Book

The Unstable Book has documentation for unstable
features.

The `rustc` Development Guide

The rustc-dev-guide
documents how the compiler works and how to contribute to it. This is useful if
you want to build or modify the Rust compiler from source (e.g. to target
something non-standard).

Specialized Rust

When using Rust in specific domains, consider using the following resources
tailored to each area.

Embedded Systems

When developing for Bare Metal or Embedded Linux systems, you may find these
resources maintained by the Embedded Working Group useful.

The Embedded Rust Book

The Embedded Rust Book is targeted at developers who are familiar with embedded
development and Rust, but who have not used Rust for embedded development.


Src Doc Intro (`src/doc/intro.md`)

% A 30-minute Introduction to Rust

This introduction is now deprecated. Please see the introduction to the book.


Src Doc Rustc Dev Guide README (`src/doc/rustc-dev-guide/README.md`)

About the `rustc-dev-guide`

This is a collaborative effort to build a guide that explains how rustc works.
The aim of the guide is to help new contributors get oriented to rustc,
as well as to help more experienced folks in figuring out
some new part of the compiler that they haven't worked on before.

For more information, see About This Guide.

For documentation on developing the standard library, see
std-dev-guide.

Contributing to the guide

The guide is useful today, but it has a lot of work still to go.

If you'd like to help improve the guide, we'd love to have you!
You can find plenty of issues on the issue
tracker
.
Just post a comment on the issue you would like to work on to make sure that we don't
accidentally duplicate work.
If you think something is missing, please open an issue about it!

See below for instructions on writing new docs.

The guide has a much lower bar for what it takes for a PR to be merged.
Check out the forge documentation for our policy.

Build Instructions

To build a local static HTML site, install mdbook with:

text
cargo install --locked mdbook mdbook-linkcheck2 mdbook-mermaid

and execute the following command in the root of the repository:

text
mdbook serve --open

For a one-off build, you can use mdbook build.

We use mdbook-linkcheck2 to validate URLs included in our documentation.
Link checking is not run by default locally, though it is in CI.
To enable it locally, set the environment variable ENABLE_LINKCHECK=1 like in the
following example.

text
ENABLE_LINKCHECK=1 mdbook serve

Editing the guide

Where to start

In general, if you don't know how the compiler works, that is not a
problem!
In that case, what we will do is to schedule a bit of time
for you to talk with someone who does know the code, or who wants
to pair with you and figure it out.
Then you can work on writing up what you learned.

Layout

The dev-guide is organized as a normal mdBook:
It has a src/SUMMARY.md table of contents, which links to individual pages under src/.
Configuration lives in book.toml.
Images are in src/img.
The build files are found in the book/html directory.

NOTE: if you do not add a page to SUMMARY.md, it will not be shown!

While writing

See the guide itself for instructions on writing new docs.

Maintaining the guide

CI

Various tools are kept under ci/.
Check links with mdbook-linkcheck2 --standalone.
Check semantic line breaks with cargo run --manifest-path ci/sembr/Cargo.toml src.
Triage `` annotations with cargo run --manifest-path ci/date-check/Cargo.toml ..

You can test those tools with cargo test --manifest-path ci/<tool>/Cargo.toml.

Synchronizing josh subtree with rustc

This repository is linked to rust-lang/rust as a josh subtree.
You can use the rustc-josh-sync tool to perform synchronization.

You can find a guide on how to perform the synchronization here.


Src Doc Rustdoc README (`src/doc/rustdoc/README.md`)

Rustdoc

This is documentation for rustdoc itself, written in mdbook format.
To build the book, use x.py doc src/doc/rustdoc.
To run doctests, use x.py test src/doc/rustdoc.


Src Doc Unstable Book README (`src/doc/unstable-book/README.md`)

Unstable Book

These are the sources for https://doc.rust-lang.org/nightly/unstable-book/.
To generate them, run ./x doc unstable-book, which will generate HTML files in build/host/doc/unstable-book using src/tools/rustbook.
If you need to change the overall structure, modify src/tools/unstable-book-gen/src/SUMMARY.md.

Note that most of this book is autogenerated by unstable-book-gen, with the exception of compiler-flags and compiler-environment-variables.
As a result, it does not integrate well with mdbook. Use ./x doc instead.


Src Doc Rustc Dev Guide Src Getting Started (`src/doc/rustc-dev-guide/src/getting-started.md`)

Getting Started

Thank you for your interest in contributing to Rust!
There are many ways to contribute, and we appreciate all of them.

For general information about how to contribute to Rust,
see Forge.
The rest of this section is about how to contribute to the compiler specifically.

This documentation is not intended to be comprehensive;
it is meant to be a quick guide for the most useful things.
For more information,
see How to build and run the compiler.

Finding help

See also "Asking
Questions"
.

Experts

Not all t-compiler members are experts on all parts of rustc;
it's a pretty large project.
To find out who could have some expertise on
different parts of the compiler, consult triagebot assign groups.
The sections that start with [assign* in triagebot.toml file.
But also, feel free to ask questions even if you can't figure out who to ping.

Another way to find experts for a given part of the compiler is to see who has made recent commits.
For example, to find people who have recently worked on name resolution since the 1.68.2 release,
you could run git shortlog -n 1.68.2.. compiler/rustc_resolve/.
Ignore any commits starting with
"Rollup merge" or commits by @bors (see CI contribution procedures for
more information about these commits).

What should I work on?

The rust monorepo is quite large and it can be difficult to know which parts need
help, or are a good starting place for beginners.
Here are some suggested starting places.

Easy or mentored issues

If you're looking for somewhere to start, check out the following issue
search
.
See the Triage for an explanation of these labels.
You can also try filtering the search to areas you're interested in.
For example:

  • label:T-compiler will only show issues related to the compiler
  • label:A-diagnostics will only show diagnostic issues

Not all important or beginner work has issue labels.
See below for how to find work that isn't labelled.

Recurring work

Some work is too large to be done by a single person.
In this case, it's common to have "Tracking issues" to co-ordinate the work between contributors.
Here are some example tracking issues where
it's easy to pick up work without a large time commitment:

  • Add recurring work items here.

If you find more recurring work, please feel free to add it here!

Diagnostic issues

Many diagnostic issues are self-contained and don't need detailed background knowledge of the
compiler.
You can see a list of diagnostic issues here.

Picking up abandoned pull requests

Sometimes, contributors send a pull request, but later find out that they don't have enough
time to work on it, or they simply are not interested in it anymore.
Such PRs are often eventually closed and they receive the S-inactive label.
You could try to examine some of these PRs and pick up the work.
You can find the list of such PRs here.

If the PR has been implemented in some other way in the meantime, the S-inactive label
should be removed from it.
If not, and it seems that there is still interest in the change,
you can try to rebase the pull request on top of the latest main branch and send a new
pull request, continuing the work on the feature.

Writing tests

Issues that have been resolved but do not have a regression test are marked with the E-needs-test label.
Writing unit tests is a low-risk,
lower-priority task that offers new contributors a great opportunity to familiarize themselves
with the testing infrastructure and contribution workflow.
You can see a list of needs test issues here.

Contributing to std (standard library)

See std-dev-guide.

Other ways to contribute

See Forge.

Cloning and Building

See "How to build and run the compiler".

Bug fixes or "normal" code changes

For most PRs, no special procedures are needed.
You can just open a PR, and it will be reviewed, approved, and merged.
This includes most bug fixes, refactorings, and other user-invisible changes.
The next few sections talk about exceptions to this rule.

Also, note that it is perfectly acceptable to open WIP PRs or GitHub Draft PRs.
Some people prefer to do this so they can get feedback along the
way or share their code with a collaborator.
Others do this so they can utilize
the CI to build and test their PR (e.g. when developing on a slow machine).

Pull requests

Pull requests (or PRs for short) are the primary mechanism we use to change Rust.
GitHub itself has some great documentation on using the Pull Request feature.
We use the "fork and pull" model,
where contributors push changes to their personal fork and create pull requests to
bring those changes into the source repository.
We have a chapter on how to use Git when contributing to Rust.

Advice for potentially large, complex, cross-cutting and/or very domain-specific changes

The compiler reviewers on rotation usually each have areas of the compiler that they know well,
but also have areas that they are not very familiar with. If your PR contains changes that are
large, complex, cross-cutting and/or highly domain-specific, it becomes very difficult to find a
suitable reviewer who is comfortable in reviewing all of the changes in such a PR. This is also
true if the changes are not only compiler-specific but also contain changes which fall under the
purview of reviewers from other teams, like the standard library team. There's a bot
which notifies the relevant teams and pings people who have set up specific alerts based on the
files modified.

Before making such changes, you are strongly encouraged to discuss your proposed changes with
the compiler team beforehand
(and with other teams that the changes would require approval
from), and work with the compiler team to see if we can help you break down a large potentially
unreviewable PR into a series of smaller more individually reviewable PRs
.

You can communicate with the compiler team by creating a #t-compiler thread on Zulip
to discuss your proposed changes.

Communicating with the compiler team beforehand helps in several ways:

  1. It increases the likelihood of your PRs being reviewed in a timely manner.
    • We can help you identify suitable reviewers before you open actual PRs, or help find
      advisors and liaisons to help you navigate the change procedures, or help with running
      try-jobs, perf runs and crater runs as suitable.
  2. It helps the compiler team track your changes.
  3. The compiler team can perform vibe checks on your changes early and often, to see if the
    direction of the changes align with what the compiler team prefers to see.
  4. Helps to avoid situations where you may have invested significant time and effort into large
    changes that the compiler team might not be willing to accept, or finding out very late that the
    changes are in a direction that the compiler team disagrees with.

Issue triage

Please see https://forge.rust-lang.org/release/issue-triaging.html.

Other Resources

This section has moved to the "About this guide" chapter.


Src Doc Rustc Dev Guide Src Overview (`src/doc/rustc-dev-guide/src/overview.md`)

Overview of the compiler

This chapter is about the overall process of compiling a program -- how everything fits together.

The Rust compiler is special in two ways: it does things to your code that
other compilers don't do (e.g. borrow-checking) and it has a lot of
unconventional implementation choices (e.g. queries).
We will talk about these in turn in this chapter, and in the rest of the guide, we will look at the
individual pieces in more detail.

What the compiler does to your code

So first, let's look at what the compiler does to your code.
For now, we will avoid mentioning how the compiler implements these steps except as needed.

Invocation

Compilation begins when a user writes a Rust source program in text and invokes
the rustc compiler on it.
The work that the compiler needs to perform is defined by command-line options.
For example, it is possible to enable nightly
features (-Z flags), perform check-only builds, or emit the LLVM
Intermediate Representation (LLVM-IR) rather than executable machine code.
The rustc executable call may be indirect through the use of cargo.

Command line argument parsing occurs in the rustc_driver.
This crate defines the compile configuration that is requested by the user and passes it
to the rest of the compilation process as a rustc_interface::Config.

Lexing and parsing

The raw Rust source text is analyzed by a low-level lexer located in rustc_lexer.
At this stage, the source text is turned into a stream of
atomic source code units known as tokens.
The lexer supports the Unicode character encoding.

The token stream passes through a higher-level lexer located in
rustc_parse to prepare for the next stage of the compile process.
The Lexer struct is used at this stage to perform a set of validations
and turn strings into interned symbols (interning is discussed later).
String interning is a way of storing only one immutable copy of each distinct string value.

The lexer has a small interface and doesn't depend directly on the diagnostic
infrastructure in rustc.
Instead it provides diagnostics as plain data which
are emitted in rustc_parse::lexer as real diagnostics.
The lexer preserves full fidelity information for both IDEs and procedural macros
(sometimes referred to as "proc-macros").

The parser translates the token stream from the lexer into an Abstract Syntax
Tree (AST)
.
It uses a recursive descent (top-down) approach to syntax analysis.
The crate entry points for the parser are the
Parser::parse_crate_mod and Parser::parse_mod
methods found in rustc_parse::parser::Parser.
The external module parsing
entry point is rustc_expand::module::parse_external_mod.
And the macro-parser entry point is Parser::parse_nonterminal.

Parsing is performed with a set of parser utility methods including bump,
check, eat, expect, look_ahead.

Parsing is organized by semantic construct.
Separate parse_* methods can be found in the rustc_parse directory.
The source file name follows the construct name.
For example, the following files are found in the parser:

This naming scheme is used across many compiler stages.
You will find either a file or directory with the same name across the parsing, lowering, type
checking, Typed High-level Intermediate Representation (THIR) lowering, and
Mid-level Intermediate Representation (MIR) building sources.

Macro-expansion, AST-validation, name-resolution, and early linting also take
place during the lexing and parsing stage.

The rustc_ast::ast::{Crate, Expr, Pat, ...} AST nodes are
returned from the parser while the standard Diag API is used for error handling.
Generally Rust's compiler will try to recover from errors
by parsing a superset of Rust's grammar, while also emitting an error type.

`AST` lowering

Next the AST is converted into High-Level Intermediate Representation
(HIR)
, a more compiler-friendly representation of the AST.
This process is called "lowering" and involves a lot of desugaring (the expansion and
formalizing of shortened or abbreviated syntax constructs) of things like loops and async fn.

We then use the HIR to do type inference (the process of automatic
detection of the type of an expression), trait solving (the process of
pairing up an impl with each reference to a trait), and type checking.
Type checking is the process of converting the types found in the HIR (hir::Ty),
which represent what the user wrote, into the internal representation used by
the compiler (Ty<'tcx>).
It's called type checking because the information
is used to verify the type safety, correctness and coherence of the types used in the program.

`MIR` lowering

The HIR is further lowered to MIR
(used for borrow checking) by constructing the THIR (an even more desugared HIR used for
pattern and exhaustiveness checking) to convert into MIR.

We do many optimizations on the MIR because it is generic and that
improves later code generation and compilation speed.
It is easier to do some optimizations at MIR level than at LLVM-IR level.
For example LLVM doesn't seem
to be able to optimize the pattern the simplify_try MIR-opt looks for.

Rust code is also monomorphized during code generation, which means making
copies of all the generic code with the type parameters replaced by concrete types.
To do this, we need to collect a list of what concrete types to generate code for.
This is called monomorphization collection and it happens at the MIR level.

Code generation

We then begin what is simply called code generation or codegen.
The code generation stage is when higher-level representations of source are
turned into an executable binary.
Since rustc uses LLVM for code generation,
the first step is to convert the MIR to LLVM-IR.
This is where the MIR is actually monomorphized.
The LLVM-IR is passed to LLVM, which does a lot more
optimizations on it, emitting machine code which is basically assembly code
with additional low-level types and annotations added (e.g. an ELF object or
WASM).
The different libraries/binaries are then linked together to produce the final binary.

How it does it

Now that we have a high-level view of what the compiler does to your code,
let's take a high-level view of how it does all that stuff.
There are a lot of constraints and conflicting goals that the compiler needs to
satisfy/optimize for.
For example,

  • Compilation speed: how fast is it to compile a program?
    More/better compile-time analyses often means compilation is slower.
    • Also, we want to support incremental compilation, so we need to take that into account.
      How can we keep track of what work needs to be redone and
      what can be reused if the user modifies their program?
      • Also we can't store too much stuff in the incremental cache because
        it would take a long time to load from disk and it could take a lot
        of space on the user's system...
  • Compiler memory usage: while compiling a program, we don't want to use more memory than we need.
  • Program speed: how fast is your compiled program?
    More/better compile-time analyses often means the compiler can do better optimizations.
  • Program size: how large is the compiled binary?
    Similar to the previous point.
  • Compiler compilation speed: how long does it take to compile the compiler?
    This impacts contributors and compiler maintenance.
  • Implementation complexity: building a compiler is one of the hardest
    things a person/group can do, and Rust is not a very simple language, so how
    do we make the compiler's code base manageable?
  • Compiler correctness: the binaries produced by the compiler should do what
    the input programs says they do, and should continue to do so despite the
    tremendous amount of change constantly going on.
  • Integration: a number of other tools need to use the compiler in
    various ways (e.g. cargo, clippy, Miri) that must be supported.
  • Compiler stability: the compiler should not crash or fail ungracefully on the stable channel.
  • Rust stability: the compiler must respect Rust's stability guarantees by not
    breaking programs that previously compiled despite the many changes that are
    always going on to its implementation.
  • Limitations of other tools: rustc uses LLVM in its backend, and LLVM has some
    strengths we leverage and some aspects we need to work around.

So, as you continue your journey through the rest of the guide, keep these things in mind.
They will often inform decisions that we make.

Intermediate representations

As with most compilers, rustc uses some intermediate representations (IRs) to
facilitate computations.
In general, working directly with the source code is extremely inconvenient and error-prone.
Source code is designed to be human-friendly while at
the same time being unambiguous, but it's less convenient for doing something
like, say, type checking.

Instead most compilers, including rustc, build some sort of IR out of the
source code which is easier to analyze.
rustc has a few IRs, each optimized for different purposes:

  • Token stream: the lexer produces a stream of tokens directly from the source code.
    This stream of tokens is easier for the parser to deal with than raw text.
  • Abstract Syntax Tree (AST): the abstract syntax tree is built from the stream
    of tokens produced by the lexer.
    It represents pretty much exactly what the user wrote.
    It helps to do some syntactic sanity
    checking (e.g. checking that a type is expected where the user wrote one).
  • High-level IR (HIR): This is a sort of desugared AST.
    It's still close to what the user wrote syntactically, but it includes some implicit things
    such as some elided lifetimes, etc. This IR is amenable to type checking.
  • Typed HIR (THIR) formerly High-level Abstract IR (HAIR): This is an
    intermediate between HIR and MIR.
    It is like the HIR but it is fully typed
    and a bit more desugared (e.g. method calls and implicit dereferences are
    made fully explicit).
    As a result, it is easier to lower to MIR from THIR than from HIR.
  • Middle-level IR (MIR): This IR is basically a Control-Flow Graph (CFG).
    A CFG is a type of diagram that shows the basic blocks of a program and how control
    flow can go between them.
    Likewise, MIR also has a bunch of basic blocks with
    simple typed statements inside them (e.g. assignment, simple computations,
    etc) and control flow edges to other basic blocks (e.g., calls, dropping
    values).
    MIR is used for borrow checking and other
    important dataflow-based checks, such as checking for uninitialized values.
    It is also used for a series of optimizations and for constant evaluation (via Miri).
    Because MIR is still generic, we can do a lot of analyses here more
    efficiently than after monomorphization.
  • LLVM-IR: This is the standard form of all input to the LLVM compiler.
    LLVM-IR is a sort of typed assembly language with lots of annotations.
    It's
    a standard format that is used by all compilers that use LLVM (e.g. the clang
    C compiler also outputs LLVM-IR).
    LLVM-IR is designed to be easy for other
    compilers to emit and also rich enough for LLVM to run a bunch of optimizations on it.

One other thing to note is that many values in the compiler are interned.
This is a performance and memory optimization in which we allocate the values in
a special allocator called an arena.
Then, we pass around references to the values allocated in the arena.
This allows us to make
sure that identical values (e.g. types in your program) are only allocated once
and can be compared cheaply by comparing pointers.
Many of the intermediate representations are interned.

Queries

The first big implementation choice is Rust's use of the query system in its compiler.
The Rust compiler is not organized as a series of passes over the code which execute sequentially.
The Rust compiler does this to make
incremental compilation possible -- that is, if the user makes a change to
their program and recompiles, we want to do as little redundant work as
possible to output the new binary.

In rustc, all the major steps above are organized as a bunch of queries that call each other.
For example, there is a query to ask for the type of something
and another to ask for the optimized MIR of a function.
These queries can call each other and are all tracked through the query system.
The results of the queries are cached on disk so that the compiler can tell which queries' results
changed from the last compilation and only redo those.
This is how incremental compilation works.

In principle, for the query-fied steps, we do each of the above for each item individually.
For example, we will take the HIR for a function and use queries
to ask for the LLVM-IR for that HIR.
This drives the generation of optimized
MIR, which drives the borrow checker, which drives the generation of MIR, and so on.

... except that this is very over-simplified.
In fact, some queries are not
cached on disk, and some parts of the compiler have to run for all code anyway
for correctness even if the code is dead code (e.g. the borrow checker). For
example, currently the mir_borrowck query is first executed on all functions
of a crate.
Then the codegen backend invokes the
collect_and_partition_mono_items query, which first recursively requests the
optimized_mir for all reachable functions, which in turn runs mir_borrowck
for that function and then creates codegen units.
This kind of split will need
to remain to ensure that unreachable functions still have their errors emitted.

Moreover, the compiler wasn't originally built to use a query system; the query
system has been retrofitted into the compiler, so parts of it are not query-fied yet.
Also, LLVM isn't our code, so that isn't querified either.
The plan is to eventually query-fy all of the steps listed in the previous section,
but as of November 2022, only the steps between HIR and
LLVM-IR are query-fied.
That is, lexing, parsing, name resolution, and macro
expansion are done all at once for the whole program.

One other thing to mention here is the all-important "typing context",
TyCtxt, which is a giant struct that is at the center of all things.
(Note that the name is mostly historic.
This is not a "typing context" in the sense of Γ or Δ from type theory.
The name is retained because that's what the name of the struct is in the source code.) All
queries are defined as methods on the TyCtxt type, and the in-memory query
cache is stored there too.
In the code, there is usually a variable called tcx which is a handle on the typing context.
You will also see lifetimes with
the name 'tcx, which means that something is tied to the lifetime of the
TyCtxt (usually it is stored or interned there).

For more information about queries in the compiler, see the queries chapter.

`ty::Ty`

Types are really important in Rust, and they form the core of a lot of compiler analyses.
The main type (in the compiler) that represents types (in the user's
program) is rustc_middle::ty::Ty.
This is so important that we have a whole chapter
on ty::Ty, but for now, we just want to mention that it exists and is the way
rustc represents types!

Also note that the rustc_middle::ty module defines the TyCtxt struct we mentioned before.

Parallelism

Compiler performance is a problem that we would like to improve on (and are always working on).
One aspect of that is parallelizing rustc itself.

Currently, there is only one part of rustc that is parallel by default:
code generation.

However, the rest of the compiler is still not yet parallel.
There have been lots of efforts spent on this, but it is generally a hard problem.
The current approach is to turn RefCells into Mutexs -- that is, we
switch to thread-safe internal mutability.
However, there are ongoing
challenges with lock contention, maintaining query-system invariants under
concurrency, and the complexity of the code base.
One can try out the current work by enabling parallel compilation in bootstrap.toml.
It's still early days,
but there are already some promising performance improvements.

Bootstrapping

rustc itself is written in Rust.
So how do we compile the compiler? We use an older compiler to compile the newer compiler.
This is called bootstrapping.

Bootstrapping has a lot of interesting implications.
For example, it means that one of the major users of Rust is the Rust compiler, so we are
constantly testing our own software ("eating our own dogfood").

For more details on bootstrapping, see the bootstrapping section of the guide.

References


Src Doc Rustc Dev Guide Src Part 2 Intro (`src/doc/rustc-dev-guide/src/part-2-intro.md`)

High-Level Compiler Architecture

The remaining parts of this guide discuss how the compiler works. They go
through everything from high-level structure of the compiler to how each stage
of compilation works. They should be friendly to both readers interested in the
end-to-end process of compilation and readers interested in learning about a
specific system they wish to contribute to. If anything is unclear, feel free
to file an issue on the rustc-dev-guide
repo
or contact the compiler
team, as detailed in this chapter from Part 1.

In this part, we will look at the high-level architecture of the compiler. In
particular, we will look at three overarching design choices that impact the
whole compiler: the query system, incremental compilation, and interning.


Src Doc Rustc Dev Guide Src Part 3 Intro (`src/doc/rustc-dev-guide/src/part-3-intro.md`)

Source Code Representation

This part describes the process of taking raw source code from the user and
transforming it into various forms that the compiler can work with easily.
These are called intermediate representations (IRs).

This process starts with compiler understanding what the user has asked for:
parsing the command line arguments given and determining what it is to compile.
After that, the compiler transforms the user input into a series of IRs that
look progressively less like what the user wrote.


Src Doc Rustc Dev Guide Src Part 4 Intro (`src/doc/rustc-dev-guide/src/part-4-intro.md`)

Analysis

This part discusses the many analyses that the compiler uses to check various
properties of the code and to inform later stages.
Typically, this is what people mean when they talk about "Rust's type system".
This includes the representation, inference, and checking of types, the trait system, and the
borrow checker.
These analyses do not happen as one big pass or set of contiguous passes.
Rather, they are spread out throughout various parts of the
compilation process and use different intermediate representations.
For example,
type checking happens on the HIR, while borrow checking happens on the MIR.
Nonetheless, for the sake of presentation, we will discuss all of these
analyses in this part of the guide.


Src Doc Rustc Dev Guide Src Part 5 Intro (`src/doc/rustc-dev-guide/src/part-5-intro.md`)

From MIR to binaries

All of the preceding chapters of this guide have one thing in common:
we never generated any executable machine code at all!
With this chapter, all of that changes.

So far,
we've shown how the compiler can take raw source code in text format
and transform it into MIR.
We have also shown how the compiler does various
analyses on the code to detect things like type or lifetime errors.
Now, we will finally take the MIR and produce some executable machine code.

NOTE: This part of a compiler is often called the backend.
The term is a bit overloaded because in the compiler source,
it usually refers to the "codegen backend" (i.e. LLVM, Cranelift, or GCC).
Usually, when you see the word "backend" in this part,
we are referring to the "codegen backend".

So what do we need to do?

  1. First, we need to collect the set of things to generate code for.
    In particular,
    we need to find out which concrete types to substitute for generic ones,
    since we need to generate code for the concrete types.
    Generating code for the concrete types
    (i.e. emitting a copy of the code for each concrete type) is called monomorphization,
    so the process of collecting all the concrete types is called monomorphization collection.
  2. Next, we need to actually lower the MIR to a codegen IR
    (usually LLVM IR) for each concrete type we collected.
  3. Finally, we need to invoke the codegen backend,
    which runs a bunch of optimization passes,
    generates executable code,
    and links together an executable binary.

The code for codegen is actually a bit complex due to a few factors:

  • Support for multiple codegen backends (LLVM, Cranelift, and GCC).
    We try to share as much backend code between them as possible,
    so a lot of it is generic over the codegen implementation.
    This means that there are often a lot of layers of abstraction.
  • Codegen happens asynchronously in another thread for performance.
  • The actual codegen is done by a third-party library (either of the 3 backends).

Generally, the rustc_codegen_ssa crate contains backend-agnostic code,
while the rustc_codegen_llvm crate contains code specific to LLVM codegen.

At a very high level, the entry point is
rustc_codegen_ssa::base::codegen_crate.
This function starts the process discussed in the rest of this chapter.


Src Doc Rustc Dev Guide Src Syntax Intro (`src/doc/rustc-dev-guide/src/syntax-intro.md`)

Syntax and the AST

Working directly with source code is very inconvenient and error-prone.
Thus, before we do anything else, we convert raw source code into an
Abstract Syntax Tree (AST).
It turns out that doing this involves a lot of work,
including lexing, parsing, macro expansion, name resolution, conditional
compilation, feature-gate checking, and validation of the AST.
In this chapter, we take a look at all of these steps.

Notably, there isn't always a clean ordering between these tasks.
For example, macro expansion relies on name resolution to resolve the names of macros and imports.
And parsing requires macro expansion, which in turn may require parsing the output of the macro.


Src Doc Style Guide Src README (`src/doc/style-guide/src/README.md`)

Rust Style Guide

Motivation - why use a formatting tool?

Formatting code is a mostly mechanical task which takes both time and mental
effort. By using an automatic formatting tool, a programmer is relieved of
this task and can concentrate on more important things.

Furthermore, by sticking to an established style guide (such as this one),
programmers don't need to formulate ad hoc style rules, nor do they need to
debate with other programmers what style rules should be used, saving time,
communication overhead, and mental energy.

Humans comprehend information through pattern matching. By ensuring that all
Rust code has similar formatting, less mental effort is required to comprehend a
new project, lowering the barrier to entry for new developers.

Thus, there are productivity benefits to using a formatting tool (such as
rustfmt), and even larger benefits by using a community-consistent
formatting, typically by using a formatting tool's default settings.

The default Rust style

The Rust Style Guide defines the default Rust style, and recommends that
developers and tools follow the default Rust style. Tools such as rustfmt use
the style guide as a reference for the default style. Everything in this style
guide, whether or not it uses language such as "must" or the imperative mood
such as "insert a space ..." or "break the line after ...", refers to the
default style.

This should not be interpreted as forbidding developers from following a
non-default style, or forbidding tools from adding any particular configuration
options.

Bugs

If the style guide differs from rustfmt, that may represent a bug in rustfmt,
or a bug in the style guide; either way, please report it to the style team or
the rustfmt team or both, for investigation and fix.

If implementing a new formatting tool based on the style guide and default Rust
style, please test it on the corpus of existing Rust code, and avoid causing
widespread breakage. The implementation and testing of such a tool may surface
bugs in either the style guide or rustfmt, as well as bugs in the tool itself.

We typically resolve bugs in a fashion that avoids widespread breakage.

Formatting conventions

Indentation and line width

  • Use spaces, not tabs.
  • Each level of indentation must be 4 spaces (that is, all indentation
    outside of string literals and comments must be a multiple of 4).
  • The maximum width for a line is 100 characters.

Block indent

Prefer block indent over visual indent:

rust
// Block indent
a_function_call(
    foo,
    bar,
);

// Visual indent
a_function_call(foo,
                bar);

This makes for smaller diffs (e.g., if a_function_call is renamed in the above
example) and less rightward drift.

Trailing commas

In comma-separated lists of any kind, use a trailing comma when followed by a
newline:

rust
function_call(
    argument,
    another_argument,
);

let array = [
    element,
    another_element,
    yet_another_element,
];

This makes moving code (e.g., by copy and paste) easier, and makes diffs
smaller, as appending or removing items does not require modifying another line
to add or remove a comma.

Blank lines

Separate items and statements by either zero or one blank lines (i.e., one or
two newlines). E.g,

rust
fn foo() {
    let x = ...;

    let y = ...;
    let z = ...;
}

fn bar() {}
fn baz() {}

Trailing whitespace

Do not include trailing whitespace on the end of any line. This includes blank
lines, comment lines, code lines, and string literals.

Note that avoiding trailing whitespace in string literals requires care to
preserve the value of the literal.

Sorting

In various cases, the default Rust style specifies to sort things. If not
otherwise specified, such sorting should be "version sorting", which ensures
that (for instance) x8 comes before x16 even though the character 1 comes
before the character 8.

For the purposes of the Rust style, to compare two strings for version-sorting:

  • Process both strings from beginning to end as two sequences of maximal-length
    chunks, where each chunk consists either of a sequence of characters other
    than ASCII digits, or a sequence of ASCII digits (a numeric chunk), and
    compare corresponding chunks from the strings.
  • To compare two numeric chunks, compare them by numeric value, ignoring
    leading zeroes. If the two chunks have equal numeric value, but different
    numbers of leading digits, and this is the first time this has happened for
    these strings, treat the chunks as equal (moving on to the next chunk) but
    remember which string had more leading zeroes.
  • To compare two chunks if both are not numeric, compare them by Unicode
    character lexicographically, with two exceptions:
    • _ (underscore) sorts immediately after (space) but before any other
      character. (This treats underscore as a word separator, as commonly used in
      identifiers.)
    • Unless otherwise specified, version-sorting should sort non-lowercase
      characters (characters that can start an UpperCamelCase identifier)
      before lowercase characters.
  • If the comparison reaches the end of the string and considers each pair of
    chunks equal:
    • If one of the numeric comparisons noted the earliest point at which one
      string had more leading zeroes than the other, sort the string with more
      leading zeroes first.
    • Otherwise, the strings are equal.

Note that there exist various algorithms called "version sorting", which
generally try to solve the same problem, but which differ in various ways (such
as in their handling of numbers with leading zeroes). This algorithm
does not purport to precisely match the behavior of any particular other
algorithm, only to produce a simple and satisfying result for Rust formatting.
In particular, this algorithm aims to produce a satisfying result for a set of
symbols that have the same number of leading zeroes, and an acceptable and
easily understandable result for a set of symbols that has varying numbers of
leading zeroes.

As an example, version-sorting will sort the following strings in the order
given:

  • _ZYXW
  • _abcd
  • A2
  • ABCD
  • Z_YXW
  • ZY_XW
  • ZY_XW
  • ZYXW
  • ZYXW_
  • a1
  • abcd
  • u_zzz
  • u8
  • u16
  • u32
  • u64
  • u128
  • u256
  • ua
  • usize
  • uz
  • v000
  • v00
  • v0
  • v0s
  • v00t
  • v0u
  • v001
  • v01
  • v1
  • v009
  • v09
  • v9
  • v010
  • v10
  • w005s09t
  • w5s009t
  • x64
  • x86
  • x86_32
  • x86_64
  • x86_128
  • x87
  • zyxw

[Module-level items](items.md)

[Statements](statements.md)

[Expressions](expressions.md)

[Types](types.md)

Comments

The following guidelines for comments are recommendations only, a mechanical
formatter might skip formatting of comments.

Prefer line comments (//) to block comments (/* ... */).

When using line comments, put a single space after the opening sigil.

When using single-line block comments, put a single space after the opening
sigil and before the closing sigil. For multi-line block comments, put a
newline after the opening sigil, and a newline before the closing sigil.

Prefer to put a comment on its own line. Where a comment follows code, put a
single space before it. Where a block comment appears inline, use surrounding
whitespace as if it were an identifier or keyword.

Examples:

rust
// A comment on an item.
struct Foo { ... }

fn foo() {} // A comment after an item.

pub fn foo(/* a comment before an argument */ x: T) {...}

Comments should usually be complete sentences. Start with a capital letter, end
with a period (.). An inline block comment may be treated as a note without
punctuation.

Source lines which are entirely a comment should be limited to 80 characters
in length (including comment sigils, but excluding indentation) or the maximum
width of the line (including comment sigils and indentation), whichever is
smaller:

rust
// This comment goes up to the ................................. 80 char margin.

{
    // This comment is .............................................. 80 chars wide.
}

{
    {
        {
            {
                {
                    {
                        // This comment is limited by the ......................... 100 char margin.
                    }
                }
            }
        }
    }
}

Doc comments

Prefer line comments (///) to block comments (/** ... */).

Prefer outer doc comments (/// or /** ... */), only use inner doc comments
(//! and /*! ... */) to write module-level or crate-level documentation.

Put doc comments before attributes.

Attributes

Put each attribute on its own line, indented to the level of the item.
In the case of inner attributes (#!), indent it to the level of the inside of
the item. Prefer outer attributes, where possible.

For attributes with argument lists, format like functions.

rust
#[repr(C)]
#[foo(foo, bar)]
#[long_multi_line_attribute(
    split,
    across,
    lines,
)]
struct CRepr {
    #![repr(C)]
    x: f32,
    y: f32,
}

For attributes with an equal sign, put a single space before and after the =,
e.g., #[foo = 42].

There must only be a single derive attribute. Note for tool authors: if
combining multiple derive attributes into a single attribute, the ordering of
the derived names must generally be preserved for correctness:
#[derive(Foo)] #[derive(Bar)] struct Baz; must be formatted to
#[derive(Foo, Bar)] struct Baz;.

*small* items

In many places in this guide we specify formatting that depends on a code
construct being small. For example, single-line vs multi-line struct
literals:

rust
// Normal formatting
Foo {
    f1: an_expression,
    f2: another_expression(),
}

// "small" formatting
Foo { f1, f2 }

We leave it to individual tools to decide on exactly what small means. In
particular, tools are free to use different definitions in different
circumstances.

Some suitable heuristics are the size of the item (in characters) or the
complexity of an item (for example, that all components must be simple names,
not more complex sub-expressions). For more discussion on suitable heuristics,
see this issue.

[Non-formatting conventions](advice.md)

[Cargo.toml conventions](cargo.md)

[Principles used for deciding these guidelines](principles.md)


Src Doc Rustc Dev Guide Src Appendix Code Index (`src/doc/rustc-dev-guide/src/appendix/code-index.md`)

Code Index

rustc has a lot of important data structures.
This is an attempt to give some guidance on where to learn more
about some of the key data structures of the compiler.

Item Kind Short description Chapter Declaration
BodyId struct One of four types of HIR node identifiers Identifiers in the HIR compiler/rustc_hir/src/hir.rs
Compiler struct Represents a compiler session and can be used to drive a compilation. The Rustc Driver and Interface compiler/rustc_interface/src/interface.rs
ast::Crate struct A syntax-level representation of a parsed crate The parser compiler/rustc_ast/src/ast.rs
hir::Crate struct A more abstract, compiler-friendly form of a crate's AST The Hir compiler/rustc_middle/src/hir/mod.rs
DefId struct One of four types of HIR node identifiers Identifiers in the HIR compiler/rustc_hir/src/def_id.rs
Diag struct A struct for a compiler diagnostic, such as an error or lint Emitting Diagnostics compiler/rustc_errors/src/diagnostic.rs
DocContext struct A state container used by rustdoc when crawling through a crate to gather its documentation Rustdoc src/librustdoc/core.rs
HirId struct One of four types of HIR node identifiers Identifiers in the HIR compiler/rustc_hir_id/src/lib.rs
Lexer struct This is the lexer used during parsing. It consumes characters from the raw source code being compiled and produces a series of tokens for use by the rest of the parser The parser compiler/rustc_parse/src/lexer/mod.rs
NodeId struct One of four types of HIR node identifiers. Being phased out Identifiers in the HIR compiler/rustc_ast/src/ast.rs
ParamEnv struct Information about generic parameters or Self, useful for working with associated or generic items Parameter Environment compiler/rustc_middle/src/ty/mod.rs
ParseSess struct This struct contains information about a parsing session The parser compiler/rustc_session/src/parse/parse.rs
Rib struct Represents a single scope of names Name resolution compiler/rustc_resolve/src/lib.rs
Session struct The data associated with a compilation session The parser, The Rustc Driver and Interface compiler/rustc_session/src/session.rs
SourceFile struct Part of the SourceMap. Maps AST nodes to their source code for a single source file. Was previously called FileMap The parser compiler/rustc_span/src/lib.rs
SourceMap struct Maps AST nodes to their source code. It is composed of SourceFiles. Was previously called CodeMap The parser compiler/rustc_span/src/source_map.rs
Span struct A location in the user's source code, used for error reporting primarily Emitting Diagnostics compiler/rustc_span/src/span_encoding.rs
TokenStream struct An abstract sequence of tokens, organized into TokenTrees The parser, Macro expansion compiler/rustc_ast/src/tokenstream.rs
TraitDef struct This struct contains a trait's definition with type information The ty modules compiler/rustc_middle/src/ty/trait_def.rs
TraitRef struct The combination of a trait and its input types (e.g. P0: Trait) Trait Solving: Goals and Clauses compiler/rustc_middle/src/ty/sty.rs
Ty<'tcx> struct This is the internal representation of a type used for type checking Type checking compiler/rustc_middle/src/ty/mod.rs
TyCtxt<'tcx> struct The "typing context". This is the central data structure in the compiler. It is the context that you use to perform all manner of queries The ty modules compiler/rustc_middle/src/ty/context.rs

Src Doc Rustc Dev Guide Src Building Quickstart (`src/doc/rustc-dev-guide/src/building/quickstart.md`)

Quickstart

This is a quickstart guide about getting the compiler running.
For more information on the individual steps, see the other pages in this chapter.

First, clone the repository:

sh
git clone https://github.com/rust-lang/rust.git
cd rust

When building the compiler, we don't use cargo directly;
instead we use a wrapper called "x", which is invoked with ./x.

We need to create a configuration for the build.
Use ./x setup to create a good default.

sh
./x setup

Then, we can build the compiler.
Use ./x build to build the compiler, standard library, and a few tools.
You can also ./x check to just check it.
All these commands can take specific components/paths as arguments, for example ./x check compiler to just check the compiler.

sh
./x build

When doing a change to the compiler that does not affect the way it compiles
the standard library (so for example, a change to an error message), use
--keep-stage-std 1 to avoid recompiling it.

After building the compiler and standard library, you now have a working compiler toolchain.
You can use it with rustup by linking it.

sh
rustup toolchain link stage1 build/host/stage1

NOTE: If you use ./x setup tools, the default stage will be set to 2 instead of 1.
Adjust your command accordingly:

sh
rustup toolchain link stage2 build/host/stage2

Now you have a toolchain called stage1 linked to your build.
You can use it to test the compiler.

sh
rustc +stage1 testfile.rs

After doing a change, you can run the compiler test suite with ./x test.

./x test runs the full test suite, which is slow and rarely what you want.
Usually, ./x test tests/ui is what you want after a compiler change, testing
all UI tests that invoke the compiler on a specific test file
and check the output.

sh
./x test tests/ui

Use --bless if you've made a change and want to update the .stderr files with the new output.

Congrats, you are now ready to make a change to the compiler!
If you have more questions, the full chapter might contain the
answers, and if it doesn't, feel free to ask for help on
Zulip.

If you use VSCode, Vim, Emacs, Helix or Zed, ./x setup will ask you if you want to
set up the editor config.
For more information, check out suggested workflows.


Src Doc Rustc Dev Guide Src Debuginfo Intro (`src/doc/rustc-dev-guide/src/debuginfo/intro.md`)

Debug info

Debug info is a collection of information generated by the compiler that allows debuggers to
correctly interpret the state of a program while it is running.
That includes things like mapping instruction addresses to lines of code in the source file,
and type layout information so that bytes in memory can be read and displayed in a meaningful way.

Debug info can be a slightly overloaded term, covering all the layers between Rust MIR, and the
end-user seeing the output of their debugger onscreen.
In brief, the stack from beginning to end is as follows:

  1. Rustc inspects the MIR and communicates the relevant source, symbol, and type information to LLVM
  2. LLVM translates this information into a target-specific debug info format during compilation
  3. A debugger reads and interprets the debug info, mapping source-lines and allowing the debugee's
    variables in memory to be located and read with the correct layout
  4. Built-in debugger formatting and styling is applied to variables
  5. User-defined scripts are run, formatting and styling the variables further
  6. The debugger frontend displays the variable to the user, possibly through the means of additional
    API layers (e.g. VSCode extension by way of the
    Debug Adapter Protocol)

NOTE: This subsection of the dev guide is perhaps more detailed than necessary. It aims to collect
a large amount of scattered information into one place and equip the reader with as firm a grasp of
the entire debug stack as possible.

If you are only interested in working on the visualizer
scripts, the information in the debugger-visualizers and
testing will suffice. If you need to make changes to Rust's debug node generation,
please see rust-codegen. All other sections are supplementary, but can be
vital to understanding some of the compromises the visualizers or codegen need to make. It can
also be valuable to know when a problem might be better solved in LLVM or the debugger itself.

DWARF

The is the primary debug info format for *-gnu targets.
It is typically bundled in with the
binary, but it can be generated as a separate file.
The DWARF standard is available here.

NOTE: To inspect DWARF debug info, gimli can be used
programatically. If you prefer a GUI, the author recommends DWEX

PDB/CodeView

The primary debug info format for *-msvc targets.
PDB is a proprietary container format created by Microsoft that, unfortunately,
has multiple meanings.
We are concerned with ordinary PDB files, as Portable PDB is used mainly for .Net applications.
PDB files are separate from the compiled binary and use the .pdb extension.

PDB files contain CodeView objects, equivalent to DWARF's tags.
CodeView, the debugger that consumed CodeView objects, was originally released in 1985.
Its original intent was for C debugging,
and was later extended to support Visual C++.
There are still minor alterations to the format to support modern architectures and languages,
but many of these changes are undocumented and/or sparsely used.

It is important to keep this context in mind when working with CodeView objects.
Due to its origins,
the "feature-set" of these objects is very limited, and focused around the core features of C.
It does not have many of the convenience or features of modern DWARF standards.
A fair number of workarounds exist within the debug info stack
to compensate for CodeView's shortcomings.

Due to its proprietary nature, it is very difficult to find information about PDB and CodeView.
Many of the sources were made at vastly different times
and contain incomplete or somewhat contradictory information.
As such, this page will aim to collect as many sources as possible.

  • TIS PE specification
    which includes a lengthy section titled "Microsoft Symbol and Type Information", detailing much of
    the CodeView format.
    The document was created in 1993, but the information is still detailed,
    accurate, and has useful diagrams.
  • LLVM
  • Microsoft
    • microsoft-pdb - A C/C++ implementation of a PDB
      reader.
      The implementation does not contain the full PDB or CodeView specification, but does
      contain enough information for other PDB consumers to be written.
      At time of writing (Nov 2025),
      this repo has been archived for several years.
    • pdb-rs - A Rust-based PDB reader and writer based on
      other publicly-available information.
      Does not guarantee stability or spec compliance.
      Also contains pdbtool, which can dump PDB files (cargo install --locked pdbtool)
    • Debug Interface Access SDK.
      While it does not document the PDB format directly, details can be gleaned from the interface
      itself.
    • PDB-Documentation - a resource compiling
      much of the publicly available information about PDB and CodeView.

Debuggers

Rust supports 3 major debuggers: GDB, LLDB, and CDB.
Each has its own set of requirements,
limitations, and quirks.
This unfortunately creates a large surface area to account for.

NOTE: CDB is a proprietary debugger created by Microsoft.
The underlying engine also powers WinDbg, KD, the Microsoft C/C++ extension for VSCode,
and part of the Visual Studio Debugger.
In these docs, it will be referred to as CDB for consistency

While GDB and LLDB do offer facilities to natively support Rust's value layout, this isn't
completely necessary.
Rust currently outputs debug info very similar to that of C++, allowing
debuggers without Rust support to work with a slightly degraded experience.
More detail will be included in later sections,
but here is a quick reference for the capabilities of each debugger:

Debugger Debug Info Format Native Rust support Expression Style Visualizer Scripts
GDB DWARF Full Rust Python
LLDB DWARF and PDB Partial C/C++ Python
CDB PDB None C/C++ Natvis

IMPORTANT: CDB can be assumed to run only on Windows. No assumptions can be made about the OS
running GDB or LLDB.

Unsupported

Below, are several unsupported debuggers that are of particular note due to their potential impact
in the future.

  • Bugstalker is an x86-64 Linux debugger written in Rust,
    specifically to debug Rust programs.
    While promising, it is still in early development.
  • RAD Debugger is a Windows-only GUI debugger.
    It has a custom debug info format that PDB is translated into.
    The project also includes a linker that can generate their new debug info format
    during the linking phase.

Src Doc Rustc Dev Guide Src Mir Index (`src/doc/rustc-dev-guide/src/mir/index.md`)

The MIR (Mid-level IR)

MIR is Rust's Mid-level Intermediate Representation.
It is constructed from HIR.
MIR was introduced in RFC 1211.
It is a radically simplified form of Rust that is used for
certain flow-sensitive safety checks – notably the borrow checker!
– and also for optimization and code generation.

If you'd like a very high-level introduction to MIR,
as well as some of the compiler concepts that it relies on (such as control-flow
graphs and desugaring), you may enjoy the rust-lang blog post that introduced MIR.

Introduction to MIR

MIR is defined in the compiler/rustc_middle/src/mir/ module,
but much of the code that manipulates it is found in compiler/rustc_mir_build,
compiler/rustc_mir_transform,
and compiler/rustc_mir_dataflow.

Some of the key characteristics of MIR are:

  • It is based on a control-flow graph.
  • It does not have nested expressions.
  • All types in MIR are fully explicit.

Key MIR vocabulary

This section introduces the key concepts of MIR, summarized here:

  • Basic blocks: units of the control-flow graph, consisting of:
    • statements: actions with one successor
    • terminators: actions with potentially multiple successors; always at the end of a block
    • (if you're not familiar with the term basic block, see the background chapter)
  • Locals: Memory locations allocated on the stack (conceptually, at least),
    such as function arguments, local variables, and temporaries.
    These are identified by an index, written with a leading underscore, like _1.
    There is also a special "local" (_0) allocated to store the return value.
  • Places: expressions that identify a location in memory, like _1 or _1.f.
  • Rvalues: expressions that produce a value.
    The "R" stands for the fact that these are the "right-hand side" of an assignment.
    • Operands: the arguments to an rvalue,
      which can either be a constant (like 22) or a place (like _1).

You can get a feeling for how MIR is constructed by translating simple
programs into MIR and reading the pretty printed output.
In fact, the playground makes this easy,
since it supplies a MIR button that will show you the MIR for your program.
Try putting this program into play (or clicking on this link),
and then clicking the "MIR" button on the top:

rust
fn main() {
    let mut vec = Vec::new();
    vec.push(1);
    vec.push(2);
}

You should see something like:

mir
// WARNING: This output format is intended for human consumers only
// and is subject to change without notice. Knock yourself out.
fn main() -> () {
    ...
}

This is the MIR format for the main function.
MIR shown by above link is optimized.
Some statements like StorageLive are removed in optimization.
This happens because the compiler notices the value is never accessed in the code.
We can use rustc [filename].rs -Z mir-opt-level=0 --emit mir to view unoptimized MIR.
This requires the nightly toolchain.

Variable declarations. If we drill in a bit,
we'll see it begins with a bunch of variable declarations.
They look like this:

mir
let mut _0: ();                      // return place
let mut _1: std::vec::Vec<i32>;      // in scope 0 at src/main.rs:2:9: 2:16
let mut _2: ();
let mut _3: &mut std::vec::Vec<i32>;
let mut _4: ();
let mut _5: &mut std::vec::Vec<i32>;

You can see that variables in MIR don't have names, they have indices,
like _0 or _1. We also intermingle the user's variables (e.g.,
_1) with temporary values (e.g., _2 or _3). You can tell apart
user-defined variables because they have debuginfo associated to them (see below).

User variable debuginfo. Below the variable declarations,
we find the only hint that _1 represents a user variable:

mir
scope 1 {
    debug vec => _1;                 // in scope 1 at src/main.rs:2:9: 2:16
}

Each debug <Name> => ; annotation describes a named user variable,
and where (i.e. the place) a debugger can find the data of that variable.
Here the mapping is trivial, but optimizations may complicate the place,
or lead to multiple user variables sharing the same place.
Additionally, closure captures are described using the same system, and so
they're complicated even without optimizations, e.g.: debug x => (*((*_1).0: &T));.

The "scope" blocks (e.g., scope 1 { .. }) describe the lexical structure of
the source program (which names were in scope when), so any part of the program
annotated with // in scope 0 would be missing vec, if you were stepping
through the code in a debugger, for example.

Basic blocks. Reading further, we see our first basic block (naturally
it may look slightly different when you view it, and I am ignoring some of the comments):

mir
bb0: {
    StorageLive(_1);
    _1 = const <std::vec::Vec<T>>::new() -> bb2;
}

A basic block is defined by a series of statements and a final terminator.
In this case, there is one statement:

mir
StorageLive(_1);

This statement indicates that the variable _1 is "live",
meaning that it may be used later – this will persist until we encounter a
StorageDead(_1) statement, which indicates that the variable _1 is done being used.
These "storage statements" are used by LLVM to allocate stack space.

The terminator of the block bb0 is the call to Vec::new:

mir
_1 = const <std::vec::Vec<T>>::new() -> bb2;

Terminators are different from statements because they can have more
than one successor – that is, control may flow to different places.
Function calls like the call to Vec::new are always
terminators because of the possibility of unwinding,
although in the case of Vec::new we are able to see that indeed unwinding is not possible,
and hence we list only one successor block, bb2.

If we look ahead to bb2, we will see it looks like this:

mir
bb2: {
    StorageLive(_3);
    _3 = &mut _1;
    _2 = const <std::vec::Vec<T>>::push(move _3, const 1i32) -> [return: bb3, unwind: bb4];
}

Here there are two statements: another StorageLive, introducing the _3 temporary,
and then an assignment:

mir
_3 = &mut _1;

Assignments in general have the form:

text
<Place> = <Rvalue>

A place is an expression like _3, _3.f or *_3 – it denotes a location in memory.
An Rvalue is an expression that creates a value: in this case,
the rvalue is a mutable borrow expression, which looks like &mut .
So we can kind of define a grammar for rvalues like so:

text
<Rvalue>  = & (mut)? <Place>
          | <Operand> + <Operand>
          | <Operand> - <Operand>
          | ...

<Operand> = Constant
          | copy Place
          | move Place

As you can see from this grammar,
rvalues cannot be nested – they can only reference places and constants.
Moreover, when you use a place,
we indicate whether we are copying it (which requires that the
place have a type T where T: Copy) or moving it (which works for a place of any type).
So, for example, if we had the expression x = a + b + c in Rust,
that would get compiled to two statements and a temporary:

mir
TMP1 = a + b
x = TMP1 + c

(Try it and see,
though you may want to do release mode to skip over the overflow checks.)

MIR data types

The MIR data types are defined in the compiler/rustc_middle/src/mir/ module.
Each of the key concepts mentioned in the previous section
maps in a fairly straightforward way to a Rust type.

The main MIR data type is Body.
It contains the data for a single
function (along with sub-instances of Mir for "promoted constants",
but you can read about those below).

  • Basic blocks: The basic blocks are stored in the field
    Body::basic_blocks; this is a vector of BasicBlockData structures.
    Nobody ever references a basic block directly: instead, we pass around BasicBlock values,
    which are newtype'd indices into this vector.
  • Statements are represented by the type Statement.
  • Terminators are represented by the Terminator.
  • Locals are represented by a newtype'd index type Local.
    The data for a local variable is found in the Body::local_decls vector.
    There is also a special constant
    RETURN_PLACE identifying the special "local" representing the return value.
  • Places are identified by the struct Place.
    There are a few fields:
    • Local variables like _1
    • Projections, which are fields or other things that "project out" from a base place.
      These are represented by the newtype'd type
      ProjectionElem. So e.g. the place _1.f is a projection,
      with f being the "projection element" and _1 being the base path.
      *_1 is also a projection,
      with the * being represented by the ProjectionElem::Deref element.
  • Rvalues are represented by the enum Rvalue.
  • Operands are represented by the enum Operand.

Representing constants

When code has reached the MIR stage, constants can generally come in two forms:
MIR constants (mir::Constant) and type system constants (ty::Const).
MIR constants are used as operands: in x + CONST, CONST is a MIR constant;
similarly, in x + 2, 2 is a MIR constant.
Type system constants are used in the type system,
in particular for array lengths, but also for const generics.

Generally, both kinds of constants can be "unevaluated" or "already evaluated".
An unevaluated constant simply stores the DefId of what needs to be evaluated
to compute this result.
An evaluated constant (a "value") has already been
computed; their representation differs between type system constants and MIR
constants: MIR constants evaluate to a mir::ConstValue; type system constants
evaluate to a ty::ValTree.

Type system constants have some more variants to support const generics: they
can refer to local const generic parameters, and they are subject to inference.
Furthermore, the mir::Constant::Ty variant lets us use an arbitrary type
system constant as a MIR constant; this happens whenever a const generic
parameter is used as an operand.

MIR constant values

In general,
a MIR constant value (mir::ConstValue) was computed by evaluating some constant the user wrote.
This const evaluation produces
a very low-level representation of the result in terms of individual bytes.
We call this an "indirect" constant (mir::ConstValue::Indirect) since the value
is stored in-memory.

However, storing everything in-memory would be awfully inefficient.
Hence there are some other variants in mir::ConstValue that can represent certain simple
and common values more efficiently.
In particular, everything that can be directly written as a literal in Rust (integers,
floats, chars, bools,
but also "string literals" and b"byte string literals") has an optimized variant that
avoids the full overhead of the in-memory representation.

ValTrees

An evaluated type system constant is a "valtree".
The ty::ValTree datastructure allows us to represent

  • arrays,
  • many structs,
  • tuples,
  • enums and,
  • most primitives.

The most important rule for this representation is that every value must be uniquely represented.
In other words, a specific value must only be representable in one specific way.
For example, there is only one way to represent an array of two integers as a ValTree:
Branch([Leaf(first_int), Leaf(second_int)]).
Even though theoretically a [u32; 2] could be encoded in a u64 and thus just be a
Leaf(bits_of_two_u32), that is not a legal construction of ValTree (and is very complex to do,
so it is unlikely anyone is tempted to do so).

These rules also mean that some values are not representable.
There can be no unions in type level constants,
as it is not clear how they should be represented,
because their active variant is unknown.
Similarly, there is no way to represent raw pointers as addresses are unknown at compile-time,
and thus we cannot make any assumptions about them.
References on the other hand can be represented,
as equality for references is defined as equality on their value,
so we ignore their address and just look at the backing value.
We must make sure that the pointer values of the references are not observable at compile time.
We thus encode &42 exactly like 42.
Any conversion from valtree back to a MIR constant value must reintroduce an actual indirection.
At codegen time,
the addresses may be deduplicated between multiple uses or not,
entirely depending on arbitrary optimization choices.

As a consequence, all decoding of ValTree must happen by matching on the type first and making
decisions depending on that.
The value itself gives no useful information without the type that belongs to it.

See the const-eval WG's docs on promotion.


Src Doc Rustc Dev Guide Src Rustc Driver Intro (`src/doc/rustc-dev-guide/src/rustc-driver/intro.md`)

`rustc_driver` and `rustc_interface`

`rustc_driver`

The rustc_driver is essentially rustc's main function.
It acts as the glue for running the various phases of the compiler in the correct order,
using the interface defined in the rustc_interface crate.
Where possible, using rustc_driver rather than rustc_interface is recommended.

The main entry point of rustc_driver is rustc_driver::compiler_entrypoint.
This builder accepts the same command-line args as rustc as well as an implementation of Callbacks and a couple of other optional options.
Callbacks is a trait that allows for custom compiler configuration,
as well as allowing custom code to run after different phases of the compilation.

`rustc_interface`

The rustc_interface crate provides a low level API to external users for manually driving the compilation process,
allowing third parties to effectively use rustc's internals as a library for analyzing a crate or for ad hoc emulating of the compiler for cases where rustc_driver is not flexible enough (i.e. rustdoc compiling code and serving output).

The main entry point of rustc_interface (rustc_interface::run_compiler) takes a configuration variable for the compiler
and a closure taking a yet unresolved Compiler.
run_compiler creates a Compiler from the configuration and passes it to the closure.
Inside the closure you can use the Compiler to call various functions to compile a crate and get the results.
You can see a minimal example of how to use rustc_interface here.

You can see an example of how to use the various functions using rustc_interface needs by looking at the rustc_driver implementation,
specifically rustc_driver_impl::compiler_entrypoint
(not to be confused with rustc_interface::run_compiler).

Warning: By its very nature, the internal compiler APIs are always going
to be unstable. That said, we do try not to break things unnecessarily.


Src Doc Rustc Dev Guide Src Tests Intro (`src/doc/rustc-dev-guide/src/tests/intro.md`)

Testing the compiler

The Rust project runs a wide variety of different tests,
orchestrated by the build system (./x test).
This section gives a brief overview of the different testing tools.
Subsequent chapters dive into running tests and adding new tests.

Kinds of tests

There are several kinds of tests to exercise things in the Rust distribution.
Almost all of them are driven by ./x test, with some exceptions noted below.

Compiletest

The main test harness for testing the compiler itself is a tool called compiletest.

compiletest supports running different styles of tests,
organized into test suites.
A test mode may provide common presets/behavior for a set of test suites.
compiletest-supported tests are located in the tests directory.

The Compiletest chapter goes into detail on how to use this tool.

Example: ./x test tests/ui

Package tests

The standard library and many of the compiler packages include typical Rust #[test] unit tests,
integration tests, and documentation tests.
You can pass a path to ./x test for almost any package in the library/ or compiler/
directory, and x will essentially run cargo test on that package.

Examples:

Command Description
./x test library/std Runs tests on std only
./x test library/core Runs tests on core only
./x test compiler/rustc_data_structures Runs tests on rustc_data_structures

The standard library relies very heavily on documentation tests to cover its functionality.
However, unit tests and integration tests can also be used as needed.
Almost all of the compiler packages have doctests disabled.

All standard library and compiler unit tests are placed in separate tests file
(which is enforced in tidy).
This ensures that when the test file is changed, the crate does not need to be recompiled.
For example:

rust,ignore
#[cfg(test)]
mod tests;

If it wasn't done this way, and you were working on something like core, that
would require recompiling the entire standard library, and the entirety of rustc.

./x test includes some CLI options for controlling the behavior with these package tests:

  • --doc — Only runs documentation tests in the package.
  • --all-targets — Run all tests except documentation tests.
  • --tests — Only runs unit and integration tests

Tidy

Tidy is a custom tool used for validating source code style and formatting conventions,
such as rejecting long lines.
There is more information in the
section on coding conventions or the Tidy Readme.

Examples: ./x test tidy

Formatting

Rustfmt is integrated with the build system to enforce uniform style across the compiler.
The formatting check is automatically run by the Tidy tool mentioned above.

Examples:

Command Description
./x fmt --check Checks formatting and exits with an error if formatting is needed.
./x fmt Runs rustfmt across the entire codebase.
./x test tidy --bless First runs rustfmt to format the codebase, then runs tidy checks.

Book documentation tests

All of the books that are published have their own tests,
primarily for validating that the Rust code examples pass.
Under the hood, these are essentially using rustdoc --test on the markdown files.
The tests can be run by passing a path to a book to ./x test.

Example: ./x test src/doc/book

Links across all documentation is validated with a link checker tool,
and it can be invoked so:

console
./x test linkchecker

This requires building all of the documentation, which might take a while.

`distcheck`

distcheck verifies that the source distribution tarball created by the build system will unpack,
build, and run all tests.

console
./x test distcheck

Tool tests

Packages that are included with Rust have all of their tests run as well.
This includes things such as cargo, clippy, rustfmt, miri, bootstrap (testing the
Rust build system itself), etc.

Most of the tools are located in the src/tools directory.
To run the tool's tests, just pass its path to ./x test.

Example: ./x test src/tools/cargo

Usually these tools involve running cargo test within the tool's directory.

If you want to run only a specified set of tests, append --test-args FILTER_NAME to the command.

Example: ./x test src/tools/miri --test-args padding

In CI, some tools are allowed to fail.
Failures send notifications to the corresponding teams, and are tracked on the toolstate website.
More information can be found in the toolstate documentation.

Ecosystem testing

Rust tests integration with real-world code to catch regressions and make
informed decisions about the evolution of the language.
There are several kinds of ecosystem tests, including Crater.
See the Ecosystem testing chapter for more details.

Performance testing

A separate infrastructure is used for testing and tracking performance of the compiler.
See the Performance testing chapter for more details.

Codegen backend testing

See Codegen backend testing.

Miscellaneous information

There are some other useful testing-related info at Misc info.

Further reading

The following blog posts may also be of interest:


Src Doc Rustc Src Codegen Options Index (`src/doc/rustc/src/codegen-options/index.md`)

Codegen Options

All of these options are passed to rustc via the -C flag, short for "codegen." You can see
a version of this list for your exact compiler by running rustc -C help.

code-model

This option lets you choose which code model to use.
Code models put constraints on address ranges that the program and its symbols may use.
With smaller address ranges machine instructions
may be able to use more compact addressing modes.

The specific ranges depend on target architectures and addressing modes available to them.
For x86 more detailed description of its code models can be found in
System V Application Binary Interface
specification.

Supported values for this option are:

  • tiny - Tiny code model.
  • small - Small code model. This is the default model for majority of supported targets.
  • kernel - Kernel code model.
  • medium - Medium code model.
  • large - Large code model.

Supported values can also be discovered by running rustc --print code-models.

codegen-units

This flag controls the maximum number of code generation units the crate is
split into. It takes an integer greater than 0.

When a crate is split into multiple codegen units, LLVM is able to process
them in parallel. Increasing parallelism may speed up compile times, but may
also produce slower code. Setting this to 1 may improve the performance of
generated code, but may be slower to compile.

The default value, if not specified, is 16 for non-incremental builds. For
incremental builds the default is 256 which allows caching to be more granular.

collapse-macro-debuginfo

This flag controls whether code locations from a macro definition are collapsed into a single
location associated with that macro's call site, when generating debuginfo for this crate.

This option, if passed, overrides both default collapsing behavior and #[collapse_debuginfo]
attributes in code.

  • y, yes, on, true: collapse code locations in debuginfo.
  • n, no, off or false: do not collapse code locations in debuginfo.
  • external: collapse code locations in debuginfo only if the macro comes from a different crate.

control-flow-guard

This flag controls whether LLVM enables the Windows Control Flow
Guard

platform security feature. This flag is currently ignored for non-Windows targets.
It takes one of the following values:

  • y, yes, on, true, checks, or no value: enable Control Flow Guard.
  • nochecks: emit Control Flow Guard metadata without runtime enforcement checks (this
    should only be used for testing purposes as it does not provide security enforcement).
  • n, no, off, false: do not enable Control Flow Guard (the default).

debug-assertions

This flag lets you turn cfg(debug_assertions) conditional
compilation
on
or off. It takes one of the following values:

  • y, yes, on, true, or no value: enable debug-assertions.
  • n, no, off or false: disable debug-assertions.

If not specified, debug assertions are automatically enabled only if the
opt-level is 0.

debuginfo

This flag controls the generation of debug information. It takes one of the
following values:

  • 0 or none: no debug info at all (the default).
  • line-directives-only: line info directives only. For the nvptx* targets this enables profiling. For other use cases, line-tables-only is the better, more compatible choice.
  • line-tables-only: line tables only. Generates the minimal amount of debug info for backtraces with filename/line number info, but not anything else, i.e. no variable or function parameter info.
  • 1 or limited: debug info without type or variable-level information.
  • 2 or full: full debug info.

Note: The -g flag is an alias for -C debuginfo=2.

default-linker-libraries

This flag controls whether or not the linker includes its default libraries.
It takes one of the following values:

  • y, yes, on, true: include default libraries.
  • n, no, off or false or no value: exclude default libraries (the default).

For example, for gcc flavor linkers, this issues the -nodefaultlibs flag to
the linker.

dlltool

On windows-gnu targets, this flag controls which dlltool rustc invokes to
generate import libraries when using the raw-dylib link kind.
It takes a path to the dlltool executable.
If this flag is not specified, a dlltool executable will be inferred based on
the host environment and target.

dwarf-version

This option controls the version of DWARF that the compiler emits, on platforms
that use DWARF to encode debug information. It takes one of the following
values:

  • 2: DWARF version 2 (the default on certain platforms, like Android).
  • 3: DWARF version 3 (the default on certain platforms, like AIX).
  • 4: DWARF version 4 (the default on most platforms, like Linux & macOS).
  • 5: DWARF version 5.

DWARF version 1 is not supported.

embed-bitcode

This flag controls whether or not the compiler embeds LLVM bitcode into object
files. It takes one of the following values:

  • y, yes, on, true or no value: put bitcode in rlibs (the default).
  • n, no, off or false: omit bitcode from rlibs.

LLVM bitcode is required when rustc is performing link-time optimization (LTO).
Embedded bitcode will appear in rustc-generated object files inside of a section
whose name is defined by the target platform. Most of the time this is .llvmbc.

The use of -C embed-bitcode=no can significantly improve compile times and
reduce generated file sizes if your compilation does not actually need bitcode
(e.g. if you're not performing LTO). For these reasons, Cargo uses -C embed-bitcode=no
whenever possible. Likewise, if you are building directly with rustc we recommend
using -C embed-bitcode=no whenever you are not using LTO.

If combined with -C lto, -C embed-bitcode=no will cause rustc to abort
at start-up, because the combination is invalid.

Note: if you're building Rust code with LTO then you probably don't even
need the embed-bitcode option turned on. You'll likely want to use
-Clinker-plugin-lto instead which skips generating object files entirely and
simply replaces object files with LLVM bitcode. The only purpose for
-Cembed-bitcode is when you're generating an rlib that is both being used
with and without LTO. For example Rust's standard library ships with embedded
bitcode since users link to it both with and without LTO.

This also may make you wonder why the default is yes for this option. The
reason for that is that it's how it was for rustc 1.44 and prior. In 1.45 this
option was added to turn off what had always been the default.

embed-metadata

This option instructs rustc to include the full metadata in rlib and dylib
crate types. The default value is yes (enabled). If disabled (no), only stub
metadata will be stored in these files, to reduce their size on disk. When using
-Cembed-metadata=no, you will probably want to use --emit=metadata to produce
the full metadata into a separate .rmeta file.

extra-filename

This option allows you to put extra data in each output filename. It takes a
string to add as a suffix to the filename. See the --emit
flag
for more information.

force-frame-pointers

This flag forces the use of frame pointers. It takes one of the following
values:

  • y, yes, on, true or no value: force-enable frame pointers.
  • n, no, off or false: do not force-enable frame pointers. This does
    not necessarily mean frame pointers will be removed.

The default behaviour, if frame pointers are not force-enabled, depends on the
target.

force-unwind-tables

This flag forces the generation of unwind tables. It takes one of the following
values:

  • y, yes, on, true or no value: Unwind tables are forced to be generated.
  • n, no, off or false: Unwind tables are not forced to be generated. If unwind
    tables are required by the target an error will be emitted.

The default if not specified depends on the target.

incremental

This flag allows you to enable incremental compilation, which allows rustc
to save information after compiling a crate to be reused when recompiling the
crate, improving re-compile times. This takes a path to a directory where
incremental files will be stored.

Using incremental compilation inhibits certain optimizations (for example by increasing the amount of codegen units) and is therefore not recommended for release builds.

instrument-coverage

This option enables instrumentation-based code coverage support. See the
chapter on instrumentation-based code coverage for more information.

Note that while the -C instrument-coverage option is stable, the profile data
format produced by the resulting instrumentation may change, and may not work
with coverage tools other than those built and shipped with the compiler.

jump-tables

This option is used to allow or prevent the LLVM codegen backend from creating
jump tables when lowering switches from Rust code.

  • y, yes, on, true or no value: allow jump tables (the default).
  • n, no, off or false: disable jump tables.

To prevent jump tables being created from Rust code, a target must ensure
all crates are compiled with jump tables disabled.

Note, in many cases the Rust toolchain is distributed with precompiled
crates, such as the core and std crates, which could possibly include
jump tables. Furthermore, this option does not guarantee a target will
be free of jump tables. They could arise from external dependencies,
inline asm, or other complicated interactions when using crates which
are compiled with jump table support.

Disabling jump tables can be used to help provide protection against
jump-oriented-programming (JOP) attacks.

This flag lets you append a single extra argument to the linker invocation.

"Append" is significant; you can pass this flag multiple times to add multiple arguments.

On Unix-like targets that use cc as the linker driver, use -Clink-arg=-Wl,$ARG to pass an argument to the actual linker.

This flag lets you append multiple extra arguments to the linker invocation. The
options should be separated by spaces.

Tries to generate and link dead code that would otherwise not be generated or
linked. It takes one of the following values:

  • y, yes, on, true or no value: try to keep dead code.
  • n, no, off or false: remove dead code (the default).

This flag was historically used to help improve some older forms of code
coverage measurement. Its use is not recommended.

This flag controls whether the linker will use libraries and objects shipped with Rust instead of
those in the system. It also controls which binary is used for the linker itself. This allows
overriding cases when detection fails or the user wants to use shipped libraries.

You can enable or disable the usage of any self-contained components using one of the following values:

  • no value: rustc will use heuristic to disable self-contained mode if system has necessary tools.
  • y, yes, on, true: use only libraries/objects shipped with Rust.
  • n, no, off or false: rely on the user or the linker to provide non-Rust libraries/objects.

It is also possible to enable or disable specific self-contained components in a more granular way.
You can pass a comma-separated list of self-contained components, individually enabled
(+component) or disabled (-component).

Currently, only the linker granular option is stabilized, and only on the x86_64-unknown-linux-gnu target:

  • linker: toggle the usage of self-contained linker binaries (linker, dlltool, and their necessary libraries)

Note that only the -linker opt-out is stable on the x86_64-unknown-linux-gnu target: +linker is
already the default on this target.

Implementation notes

On the x86_64-unknown-linux-gnu target, when using the default linker flavor (using cc as the
linker driver) and linker features (to try using lld), rustc will try to use the self-contained
linker by passing a -B /path/to/sysroot/ link argument to the driver to find rust-lld in the
sysroot. For backwards-compatibility, and to limit name and PATH collisions, this is done using a
shim executable (the lld-wrapper tool) that forwards execution to the rust-lld executable itself.

linker

This flag controls which linker rustc invokes to link your code. It takes a
path to the linker executable. If this flag is not specified, the linker will
be inferred based on the target. See also the linker-flavor
flag for another way to specify the linker.

Note that on Unix-like targets (for example, *-unknown-linux-gnu or *-unknown-freebsd)
the C compiler (for example cc or clang) is used as the "linker" here, serving as a linker driver.
It will invoke the actual linker with all the necessary flags to be able to link against the system libraries like libc.

linker-features

The -Clinker-features flag allows enabling or disabling specific features used during linking.

These feature flags are a flexible extension mechanism that is complementary to linker flavors,
designed to avoid the combinatorial explosion of having to create a new set of flavors for each
linker feature we'd want to use.

The flag accepts a comma-separated list of features, individually enabled (+feature) or disabled
(-feature).

Currently only one is stable, and only on the x86_64-unknown-linux-gnu target:

  • lld: to toggle trying to use the lld linker, either the system-installed binary, or the self-contained
    rust-lld linker (via the -Clink-self-contained=+linker flag).

For example, use:

  • -Clinker-features=+lld to opt into using the lld linker, when possible (see the Implementation notes below)
  • -Clinker-features=-lld to opt out instead, for targets where it is configured as the default linker

Note that only the -lld opt-out is stable on the x86_64-unknown-linux-gnu target: +lld is
already the default on this target.

Implementation notes

On the x86_64-unknown-linux-gnu target, when using the default linker flavor (using cc as the
linker driver), rustc will try to use lld by passing a -fuse-ld=lld link argument to the driver.
rustc will also try to detect if that causes an error during linking (for example, if GCC is too
old to understand the flag, and returns an error) and will then retry linking without this argument,
as a fallback.

If the user also passes a -Clink-arg=-fuse-ld=$value, both will be given to the linker
driver but the user's will be passed last, and would generally have priority over rustc's.

linker-flavor

This flag controls the linker flavor used by rustc. If a linker is given with
the -C linker flag, then the linker flavor is inferred from the
value provided. If no linker is given then the linker flavor is used to
determine the linker to use. Every rustc target defaults to some linker
flavor. Valid options are:

  • em: use Emscripten emcc.
  • gcc: use the cc executable, which is typically gcc or clang on many systems.
  • ld: use the ld executable.
  • msvc: use the link.exe executable from Microsoft Visual Studio MSVC.
  • wasm-ld: use the wasm-ld
    executable, a port of LLVM lld for WebAssembly.
  • ld64.lld: use the LLVM lld executable with the -flavor darwin
    flag
    for Apple's ld.
  • ld.lld: use the LLVM lld executable with the -flavor gnu
    flag
    for GNU binutils' ld.
  • lld-link: use the LLVM lld executable with the -flavor link
    flag
    for Microsoft's link.exe.

linker-plugin-lto

This flag defers LTO optimizations to the linker. See
linker-plugin-LTO for more details. It takes one of
the following values:

  • y, yes, on, true or no value: enable linker plugin LTO.
  • n, no, off or false: disable linker plugin LTO (the default).
  • A path to the linker plugin.

More specifically this flag will cause the compiler to replace its typical
object file output with LLVM bitcode files. For example an rlib produced with
-Clinker-plugin-lto will still have *.o files in it, but they'll all be LLVM
bitcode instead of actual machine code. It is expected that the native platform
linker is capable of loading these LLVM bitcode files and generating code at
link time (typically after performing optimizations).

Note that rustc can also read its own object files produced with
-Clinker-plugin-lto. If an rlib is only ever going to get used later with a
-Clto compilation then you can pass -Clinker-plugin-lto to speed up
compilation and avoid generating object files that aren't used.

llvm-args

This flag can be used to pass a list of arguments directly to LLVM.

The list must be separated by spaces.

Pass --help to see a list of options.

Because this flag directly talks to LLVM, it is not subject to the usual stability guarantees of rustc's CLI interface.

lto

This flag controls whether LLVM uses link time
optimizations
to produce
better optimized code, using whole-program analysis, at the cost of longer
linking time. It takes one of the following values:

  • y, yes, on, true, fat, or no value: perform "fat" LTO which attempts to
    perform optimizations across all crates within the dependency graph.
  • thin: perform "thin"
    LTO
    .
    This is similar to "fat", but takes substantially less time to run while
    still achieving performance gains similar to "fat".
    For larger projects like the Rust compiler, ThinLTO can even result in better performance than fat LTO.
  • n, no, off, false: disables LTO.

If -C lto is not specified, then the compiler will attempt to perform "thin
local LTO" which performs "thin" LTO on the local crate only across its
codegen units. When -C lto is not specified, LTO is
disabled if codegen units is 1 or optimizations are disabled (-C opt-level=0). That is:

  • When -C lto is not specified:
    • codegen-units=1: disable LTO.
    • opt-level=0: disable LTO.
  • When -C lto is specified:
    • lto: 16 codegen units, perform fat LTO across crates.
    • codegen-units=1 + lto: 1 codegen unit, fat LTO across crates.

See also linker-plugin-lto for cross-language LTO.

metadata

This option allows you to control the metadata used for symbol mangling. This
takes a space-separated list of strings. Mangled symbols will incorporate a
hash of the metadata. This may be used, for example, to differentiate symbols
between two different versions of the same crate being linked.

no-prepopulate-passes

This flag tells the pass manager to use an empty list of passes, instead of the
usual pre-populated list of passes.

When combined with -O --emit llvm-ir, it can be used to see the optimized LLVM IR emitted by rustc before any optimizations are applied by LLVM.

no-redzone

This flag allows you to disable the
red zone
. It takes one
of the following values:

  • y, yes, on, true or no value: disable the red zone.
  • n, no, off or false: enable the red zone.

The default behaviour, if the flag is not specified, depends on the target.

no-vectorize-loops

This flag disables loop
vectorization
.

no-vectorize-slp

This flag disables vectorization using
superword-level
parallelism
.

opt-level

This flag controls the optimization level.

  • 0: no optimizations, also turns on
    cfg(debug_assertions) (the default).
  • 1: basic optimizations.
  • 2: some optimizations.
  • 3: all optimizations.
  • s: optimize for binary size.
  • z: optimize for binary size, but more aggressively. Often results in larger binaries than s

Note: The -O flag is an alias for -C opt-level=3.

The default is 0.

overflow-checks

This flag allows you to control the behavior of runtime integer
overflow
. When
overflow-checks are enabled, a panic will occur on overflow. This flag takes
one of the following values:

  • y, yes, on, true or no value: enable overflow checks.
  • n, no, off or false: disable overflow checks.

If not specified, overflow checks are enabled if
debug-assertions are enabled, disabled otherwise.

panic

This option lets you control what happens when the code panics.

  • abort: terminate the process upon panic
  • immediate-abort: terminate the process upon panic, and do not call any panic hooks
  • unwind: unwind the stack upon panic

If not specified, the default depends on the target.

If any crate in the crate graph uses abort, the final binary (bin, dylib, cdylib, staticlib) must also use abort.
If any crate in the crate graph uses immediate-abort, every crate in the graph must use immediate-abort.
If std is used as a dylib with unwind, the final binary must also use unwind.

passes

This flag can be used to add extra LLVM
passes
to the compilation.

The list must be separated by spaces.

See also the no-prepopulate-passes flag.

Because this flag directly talks to LLVM, it not subject to the usual stability guarantees of rustc's CLI interface.

prefer-dynamic

By default, rustc prefers to statically link dependencies. This option will
indicate that dynamic linking should be used if possible if both a static and
dynamic versions of a library are available.

There is an internal algorithm
for determining whether or not it is possible to statically or dynamically link
with a dependency.

This flag takes one of the following values:

  • y, yes, on, true or no value: prefer dynamic linking.
  • n, no, off or false: prefer static linking (the default).

profile-generate

This flag allows for creating instrumented binaries that will collect
profiling data for use with profile-guided optimization (PGO). The flag takes
an optional argument which is the path to a directory into which the
instrumented binary will emit the collected data. See the chapter on
profile-guided optimization for more information.

profile-sample-use

This flag specifies the profiling data file to be used for sample-based
profile-guided optimization (SPGO). The flag takes a mandatory argument which
is the path to a valid .prof file. See the chapter on
profile-guided optimization for more information.
The -Zdebuginfo-for-profiling can be used to improve the quality of the
profiling data.

profile-use

This flag specifies the profiling data file to be used for profile-guided
optimization (PGO). The flag takes a mandatory argument which is the path
to a valid .profdata file. See the chapter on
profile-guided optimization for more information.

relocation-model

This option controls generation of
position-independent code (PIC).

Supported values for this option are:

Primary relocation models

  • static - non-relocatable code, machine instructions may use absolute addressing modes.

  • pic - fully relocatable position independent code,
    machine instructions need to use relative addressing modes.
    Equivalent to the "uppercase" -fPIC or -fPIE options in other compilers,
    depending on the produced crate types.
    This is the default model for majority of supported targets.

  • pie - position independent executable, relocatable code but without support for symbol
    interpositioning (replacing symbols by name using LD_PRELOAD and similar). Equivalent to the "uppercase" -fPIE option in other compilers. pie
    code cannot be linked into shared libraries (you'll get a linking error on attempt to do this).

Special relocation models

  • dynamic-no-pic - relocatable external references, non-relocatable code.
    Only makes sense on Darwin and is rarely used.
    If StackOverflow tells you to use this as an opt-out of PIC or PIE, don't believe it,
    use -C relocation-model=static instead.
  • ropi, rwpi and ropi-rwpi - relocatable code and read-only data, relocatable read-write data,
    and combination of both, respectively.
    Only makes sense for certain embedded ARM targets.
  • default - relocation model default to the current target.
    Only makes sense as an override for some other explicitly specified relocation model
    previously set on the command line.

Supported values can also be discovered by running rustc --print relocation-models.

Linking effects

In addition to codegen effects, relocation-model has effects during linking.

If the relocation model is pic and the current target supports position-independent executables
(PIE), the linker will be instructed (-pie) to produce one.
If the target doesn't support both position-independent and statically linked executables,
then -C target-feature=+crt-static "wins" over -C relocation-model=pic,
and the linker is instructed (-static) to produce a statically linked
but not position-independent executable.

relro-level

This flag controls what level of RELRO (Relocation Read-Only) is enabled. RELRO is an exploit
mitigation which makes the Global Offset Table (GOT) read-only.

Supported values for this option are:

  • off: Dynamically linked functions are resolved lazily and the GOT is writable.
  • partial: Dynamically linked functions are resolved lazily and written into the Procedure
    Linking Table (PLT) part of the GOT (.got.plt). The non-PLT part of the GOT (.got) is made
    read-only and both are moved to prevent writing from buffer overflows.
  • full: Dynamically linked functions are resolved at the start of program execution and the
    Global Offset Table (.got/.got.plt) is populated eagerly and then made read-only. The GOT is
    also moved to prevent writing from buffer overflows. Full RELRO uses more memory and increases
    process startup time.

This flag is ignored on platforms where RELRO is not supported (targets which do not use the ELF
binary format), such as Windows or macOS. Each rustc target has its own default for RELRO. rustc
enables Full RELRO by default on platforms where it is supported.

remark

This flag lets you print remarks for optimization passes.

The list of passes should be separated by spaces.

all will remark on every pass.

rpath

This flag controls whether rustc sets an rpath for the binary.
It takes one of the following values:

  • y, yes, on, true or no value: enable rpath.
  • n, no, off or false: disable rpath (the default).

This flag only does something on Unix-like platforms (Mach-O and ELF), it is ignored on other platforms.

If enabled, rustc will add output-relative (using @load_path on Mach-O and $ORIGIN on ELF respectively) rpaths to all dylib dependencies.

For example, for the following directory structure, with libdep.so being a dylib crate compiled with -Cprefer-dynamic:

text
dep
 |- libdep.so
a.rs

rustc a.rs --extern dep=dep/libdep.so -Crpath will, on x86-64 Linux, result in approximately the following DT_RUNPATH: $ORIGIN/dep:$ORIGIN/$RELATIVE_PATH_TO_SYSROOT/lib/rustlib/x86_64-unknown-linux-gnu/lib (where RELATIVE_PATH_TO_SYSROOT depends on the build directory location).

This is primarily useful for local development, to ensure that all the dylib dependencies can be found appropriately.

To set the rpath to a different value (which can be useful for distribution), -Clink-arg with a platform-specific linker argument can be used to set the rpath directly.

save-temps

This flag controls whether temporary files generated during compilation are
deleted once compilation finishes. It takes one of the following values:

  • y, yes, on, true or no value: save temporary files.
  • n, no, off or false: delete temporary files (the default).

split-debuginfo

This option controls the emission of "split debuginfo" for debug information
that rustc generates. The default behavior of this option is
platform-specific, and not all possible values for this option work on all
platforms. Possible values are:

  • off - This is the default for platforms with ELF binaries and windows-gnu
    (not Windows MSVC and not macOS). This typically means that DWARF debug
    information can be found in the final artifact in sections of the executable.
    This option is not supported on Windows MSVC. On macOS this options prevents
    the final execution of dsymutil to generate debuginfo.

  • packed - This is the default for Windows MSVC and macOS. The term
    "packed" here means that all the debug information is packed into a separate
    file from the main executable. On Windows MSVC this is a *.pdb file, on
    macOS this is a *.dSYM folder, and on other platforms this is a *.dwp
    file.

  • unpacked - This means that debug information will be found in separate
    files for each compilation unit (object file). This is not supported on
    Windows MSVC. On macOS this means the original object files will contain
    debug information. On other Unix platforms this means that *.dwo files will
    contain debug information.

Note that all three options are supported on Linux and Apple platforms,
packed is supported on Windows-MSVC, and all other platforms support off.
Attempting to use an unsupported option requires using the nightly channel
with the -Z unstable-options flag.

strip

The option -C strip=val controls stripping of debuginfo and similar auxiliary
data from binaries during linking.

Supported values for this option are:

  • none - debuginfo and symbols are not modified.
  • debuginfo - debuginfo sections and debuginfo symbols from the symbol table
    section are stripped at link time and are not copied to the produced binary.
    This should leave backtraces mostly-intact but may make using a debugger like
    gdb or lldb ineffectual. Prior to 1.79, this unintentionally disabled the
    generation of *.pdb files on MSVC, resulting in the absence of symbols.
  • symbols - same as debuginfo, but the rest of the symbol table section is
    stripped as well, depending on platform support. On platforms which depend on
    this symbol table for backtraces, profiling, and similar, this can affect
    them so negatively as to make the trace incomprehensible. Programs which may
    be combined with others, such as CLI pipelines and developer tooling, or even
    anything which wants crash-reporting, should usually avoid -Cstrip=symbols.

Note that, at any level, removing debuginfo only necessarily impacts "friendly"
introspection. -Cstrip cannot be relied on as a meaningful security or
obfuscation measure, as disassemblers and decompilers can extract considerable
information even in the absence of symbols.

symbol-mangling-version

This option controls the name mangling format for encoding Rust item names
for the purpose of generating object code and linking.

Supported values for this option are:

  • v0 — The "v0" mangling scheme.

The default, if not specified, will use a compiler-chosen default which may
change in the future.

See the Symbol Mangling chapter for details on symbol mangling and the mangling format.

target-cpu

This instructs rustc to generate code specifically for a particular processor.

You can run rustc --print target-cpus to see the valid options to pass
and the default target CPU for the current build target.
Each target has a default base CPU. Special values include:

  • native can be passed to use the processor of the host machine.
  • generic refers to an LLVM target with minimal features but modern tuning.

target-feature

Individual targets will support different features; this flag lets you control
enabling or disabling a feature. Each feature should be prefixed with a + to
enable it or - to disable it.

Features from multiple -C target-feature options are combined.
Multiple features can be specified in a single option by separating them
with commas - -C target-feature=+x,-y.
If some feature is specified more than once with both + and -,
then values passed later override values passed earlier.
For example, -C target-feature=+x,-y,+z -Ctarget-feature=-x,+y
is equivalent to -C target-feature=-x,+y,+z.

To see the valid options and an example of use, run rustc --print target-features.

Using this flag is unsafe and might result in undefined runtime
behavior
.

See also the target_feature
attribute

for controlling features per-function.

This also supports the feature +crt-static and -crt-static to control
static C runtime linkage.

Each target and target-cpu has a default set of enabled
features.

tune-cpu

This instructs rustc to schedule code specifically for a particular
processor. This does not affect the compatibility (instruction sets or ABI),
but should make your code slightly more efficient on the selected CPU.

The valid options are the same as those for target-cpu.
The default is None, which LLVM translates as the target-cpu.

This is an unstable option. Use -Z tune-cpu=machine to specify a value.

Due to limitations in LLVM (12.0.0-git9218f92), this option is currently
effective only for x86 targets.


Src Doc Rustc Src Lints Index (`src/doc/rustc/src/lints/index.md`)

Lints

In software, a "lint" is a tool used to help improve your source code. The
Rust compiler contains a number of lints, and when it compiles your code, it will
also run the lints. These lints may produce a warning, an error, or nothing at all,
depending on how you've configured things.

Here's a small example:

bash
$ cat main.rs
fn main() {
    let x = 5;
}
$ rustc main.rs
warning: unused variable: `x`
 --> main.rs:2:9
  |
2 |     let x = 5;
  |         ^
  |
  = note: `#[warn(unused_variables)]` on by default
  = note: to avoid this warning, consider using `_x` instead

This is the unused_variables lint, and it tells you that you've introduced
a variable that you don't use in your code. That's not wrong, so it's not
an error, but it might be a bug, so you get a warning.

Future-incompatible lints

Sometimes the compiler needs to be changed to fix an issue that can cause
existing code to stop compiling. "Future-incompatible" lints are issued in
these cases to give users of Rust a smooth transition to the new behavior.
Initially, the compiler will continue to accept the problematic code and issue
a warning. The warning has a description of the problem, a notice that this
will become an error in the future, and a link to a tracking issue that
provides detailed information and an opportunity for feedback. This gives
users some time to fix the code to accommodate the change. After some time,
the warning may become an error.

The following is an example of what a future-incompatible looks like:

text
warning: borrow of packed field is unsafe and requires unsafe function or block (error E0133)
  --> lint_example.rs:11:13
   |
11 |     let y = &x.data.0;
   |             ^^^^^^^^^
   |
   = note: `#[warn(safe_packed_borrows)]` on by default
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #46043 <https://github.com/rust-lang/rust/issues/46043>
   = note: fields of packed structs might be misaligned: dereferencing a misaligned pointer or even just creating a misaligned reference is undefined behavior

For more information about the process and policy of future-incompatible
changes, see RFC 1589.


Src Doc Rustc Src Symbol Mangling Index (`src/doc/rustc/src/symbol-mangling/index.md`)

Symbol Mangling

Symbol name mangling is used by rustc to encode a unique name for symbols that are used during code generation.
The encoded names are used by the linker to associate the name with the thing it refers to.

The method for mangling the names can be controlled with the -C symbol-mangling-version option.

Per-item control

The #[no_mangle] attribute can be used on items to disable name mangling on that item.

The #[export_name]attribute can be used to specify the exact name that will be used for a function or static.

Items listed in an extern block use the identifier of the item without mangling to refer to the item.
The #[link_name] attribute can be used to change that name.

WebAssembly import modules

On WebAssembly targets, foreign items in extern blocks can use the same import name without
conflicting when they use different #[link(wasm_import_module = "...")]
values.

Decoding

The encoded names may need to be decoded in some situations.
For example, debuggers and other tooling may need to demangle the name so that it is more readable to the user.
Recent versions of gdb and lldb have built-in support for demangling Rust identifiers.
In situations where you need to do your own demangling, the rustc-demangle crate can be used to programmatically demangle names.
rustfilt is a CLI tool which can demangle names.

An example of running rustfilt:

text
$ rustfilt _RNvCskwGfYPst2Cb_3foo16example_function
foo::example_function

Mangling versions

There is currently one stable mangling scheme version - v0. On nightly builds, rustc supports
switching back to the legacy mangling scheme using -C symbol-mangling-version. The v0
mangling scheme is described in the v0 Symbol Format chapter.


Src Doc Rustc Src Targets Index (`src/doc/rustc/src/targets/index.md`)

Targets

rustc is a cross-compiler by default. This means that you can use any compiler to build for any
architecture. The list of targets are the possible architectures that you can build for. See
the Platform Support page for a detailed list of targets, or
Built-in Targets for instructions on how to view what is available for your version
of rustc.

To see all the options that you can set with a target, see the docs
here.

To compile to a particular target, use the --target flag:

bash
$ rustc src/main.rs --target=wasm32-unknown-unknown

Target Features

x86, and ARMv8 are two popular CPU architectures. Their instruction sets form a common baseline across most CPUs. However, some CPUs extend these with custom instruction sets, e.g. vector (AVX), bitwise manipulation (BMI) or cryptographic (AES).

Developers, who know on which CPUs their compiled code is going to run can choose to add (or remove) CPU specific instruction sets via the -C target-feature=val flag.

Please note, that this flag is generally considered as unsafe. More details can be found in this section.


Src Doc Rustc Src Tests Index (`src/doc/rustc/src/tests/index.md`)

Tests

rustc has a built-in facility for building and running tests for a crate.
More information about writing and running tests may be found in the Testing
Chapter
of the Rust Programming Language book.

Tests are written as free functions with the #[test]
attribute
. For example:

rust
#[test]
fn it_works() {
    assert_eq!(2 + 2, 4);
}

Tests "pass" if they return without an error. They "fail" if they panic, or
return a type such as Result that implements the Termination trait
with a non-zero value.

By passing the --test option to rustc, the compiler will build the crate
in a special mode to construct an executable that will run the tests in the
crate. The --test flag will make the following changes:

  • The crate will be built as a bin crate type, forcing it to be an
    executable.
  • Links the executable with libtest, the test harness that is part of the
    standard library, which handles running the tests.
  • Synthesizes a main function which will process command-line arguments
    and run the tests. This new main function will replace any existing main
    function as the entry point of the executable, though the existing main
    will still be compiled.
  • Enables the test cfg option, which allows your code to use conditional
    compilation to detect if it is being built as a test.
  • Enables building of functions annotated with the test
    and bench attributes, which will be run by the test
    harness.

After the executable is created, you can run it to execute the tests and
receive a report on what passes and fails. If you are using Cargo to manage
your project, it has a built-in cargo test command which handles all of
this automatically. An example of the output looks like this:

text
running 4 tests
test it_works ... ok
test check_valid_args ... ok
test invalid_characters ... ok
test walks_the_dog ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Note: Tests must be built with the unwind panic
strategy
. This is because all tests run in the same
process, and they are intended to catch panics, which is not possible with
the abort strategy. See the unstable -Z panic-abort-tests option for
experimental support of the abort strategy by spawning tests in separate
processes.

Test attributes

Tests are indicated using attributes on free functions. The following
attributes are used for testing, see the linked documentation for more
details:

  • #[test] — Indicates a function is a test to be run.
  • #[bench] — Indicates a function is a benchmark to be
    run. Benchmarks are currently unstable and only available in the nightly
    channel, see the unstable docs for more details.
  • #[should_panic] — Indicates that the test
    function will only pass if the function panics.
  • #[ignore] — Indicates that the test function will be
    compiled, but not run by default. See the --ignored and
    --include-ignored options to run these tests.

CLI arguments

The libtest harness has several command-line arguments to control its
behavior.

Note: When running with cargo test, the libtest CLI arguments must be
passed after the -- argument to differentiate between flags for Cargo and
those for the harness. For example: cargo test -- --no-capture

Filters

Positional arguments (those without a - prefix) are treated as filters which
will only run tests whose name matches one of those strings. The filter will
match any substring found in the full path of the test function. For example,
if the test function it_works is located in the module
utils::paths::tests, then any of the filters works, path, utils::, or
utils::paths::tests::it_works will match that test.

See Selection options for more options to control which
tests are run.

Action options

The following options perform different actions other than running tests.

`--list`

Prints a list of all tests and benchmarks. Does not run any of the tests.
Filters can be used to list only matching tests.

`-h`, `--help`

Displays usage information and command-line options.

Selection options

The following options change how tests are selected.

`--test`

This is the default mode where all tests will be run as well as running all
benchmarks with only a single iteration (to ensure the benchmark works,
without taking the time to actually perform benchmarking). This can be
combined with the --bench flag to run both tests and perform full
benchmarking.

`--bench`

This runs in a mode where tests are ignored, and only runs benchmarks. This
can be combined with --test to run both benchmarks and tests.

`--exact`

This forces filters to match the full path of the test exactly.
For example, if the test it_works is in the module utils::paths::tests,
then only the string utils::paths::tests::it_works will match that test.

`--skip` _FILTER_

Skips any tests whose name contains the given FILTER string. This flag may
be passed multiple times.

`--ignored`

Runs only tests that are marked with the ignore
attribute
.

`--include-ignored`

Runs both ignored and non-ignored tests.

`--exclude-should-panic`

Excludes tests marked with the should_panic
attribute
.

⚠️ 🚧 This option is unstable, and requires the -Z unstable-options flag. See tracking issue
#82348
for more information.

Execution options

The following options affect how tests are executed.

`--fail-fast`

Stops tests after the first failure.

If running tests in parallel (which is the default), then tests that have already been started on
other threads will be allowed to run to completion before the process exits.

Note that when running tests in parallel, the test execution order is non-deterministic:
if multiple tests would fail, the first failure encountered will be reported.

⚠️ 🚧 This requires the -Z unstable-options flag.

`--test-threads` _NUM_THREADS_

Sets the number of threads to use for running tests in parallel. By default,
uses the amount of concurrency available on the hardware as indicated by
available_parallelism.

Deprecated: this can also be specified with the RUST_TEST_THREADS environment variable.

`--force-run-in-process`

Forces the tests to run in a single process when using the abort panic
strategy
.

⚠️ 🚧 This only works with the unstable -Z panic-abort-tests option, and
requires the -Z unstable-options flag. See tracking issue
#67650
for more information.

`--ensure-time`

⚠️ 🚧 This option is unstable, and requires the -Z unstable-options flag. See tracking issue
#64888
and the unstable
docs
for more information.

`--shuffle`

Runs the tests in random order, as opposed to the default alphabetical order.

Deprecated: this may also be specified by setting the RUST_TEST_SHUFFLE environment
variable to anything but 0.

The random number generator seed that is output can be passed to
--shuffle-seed to run the tests in the same order
again.

Note that --shuffle does not affect whether the tests are run in parallel. To
run the tests in random order sequentially, use --shuffle --test-threads 1.

⚠️ 🚧 This option is unstable, and requires the -Z unstable-options flag. See tracking issue
#89583
for more information.

`--shuffle-seed` _SEED_

Like --shuffle, but seeds the random number generator with
SEED. Thus, calling the test harness with --shuffle-seed SEED twice runs
the tests in the same order both times.

SEED is any 64-bit unsigned integer, for example, one produced by
--shuffle.

Deprecated: this can also be specified with the RUST_TEST_SHUFFLE_SEED environment
variable.

⚠️ 🚧 This option is unstable, and requires the -Z unstable-options flag. See tracking issue
#89583
for more information.

Output options

The following options affect the output behavior.

`-q`, `--quiet`

Displays one character per test instead of one line per test. This is an alias
for --format=terse.

`--no-capture`

Does not capture the stdout and stderr of the test, and allows tests to print
to the console. Usually the output is captured, and only displayed if the test
fails.

Deprecated: this may also be specified by setting the RUST_TEST_NOCAPTURE environment
variable to anything but 0.

--nocapture is a deprecated alias for --no-capture.

`--show-output`

Displays the stdout and stderr of successful tests after all tests have run.

Contrast this with --no-capture which allows tests to print
while they are running, which can cause interleaved output if there are
multiple tests running in parallel, --show-output ensures the output is
contiguous, but requires waiting for all tests to finish.

`--color` _COLOR_

Control when colored terminal output is used. Valid options:

  • auto: Colorize if stdout is a tty and --no-capture is not
    used. This is the default.
  • always: Always colorize the output.
  • never: Never colorize the output.

`--format` _FORMAT_

Controls the format of the output. Valid options:

  • pretty: This is the default format, with one line per test.
  • terse: Displays only a single character per test. --quiet
    is an alias for this option.
  • json: Emits JSON objects, one per line. ⚠️ 🚧 This option is
    unstable, and requires the -Z unstable-options flag.
    See tracking issue #49359
    for more information.

`--logfile` _PATH_

Writes the results of the tests to the given file.

This option is deprecated.

`--report-time`

⚠️ 🚧 This option is unstable, and requires the -Z unstable-options flag. See tracking issue
#64888
and the unstable
docs
for more information.

Unstable options

Some CLI options are added in an "unstable" state, where they are intended for
experimentation and testing to determine if the option works correctly, has
the right design, and is useful. The option may not work correctly, break, or
change at any time. To signal that you acknowledge that you are using an
unstable option, they require passing the -Z unstable-options command-line
flag.

Benchmarks

The libtest harness supports running benchmarks for functions annotated with
the #[bench] attribute. Benchmarks are currently unstable, and only
available on the nightly channel. More information may be found in the
unstable book.

Custom test frameworks

Experimental support for using custom test harnesses is available on the
nightly channel. See tracking issue
#50297
and the
custom_test_frameworks documentation for more information.


Src Tools Rust Analyzer Docs Book README (`src/tools/rust-analyzer/docs/book/README.md`)

rust-analyzer documentation

The rust analyzer manual uses mdbook.

Quick start

To run the documentation site locally:

bash
cargo install --locked mdbook
cargo xtask codegen
cd docs/book
mdbook serve
# make changes to documentation files in doc/book/src
# ...

mdbook will rebuild the documentation as changes are made.

Making updates

While not required, installing the mdbook binary can be helpful in order to see the changes.
Start with the mdbook User Guide to familiarize yourself with the tool.

Generated documentation

Four sections are generated dynamically: assists, configuration, diagnostics and features. Their content is found in the generated.md files
of the respective book section, for example src/configuration_generated.md, and are included in the book via mdbook's
include functionality. Generated files can be rebuilt by running the various
test cases that generate them, or by simply running all of the rust-analyzer tests with cargo test and cargo xtask codegen.


Src Doc Rustc Dev Guide Src Building Bootstrapping Intro (`src/doc/rustc-dev-guide/src/building/bootstrapping/intro.md`)

Bootstrapping the compiler

Bootstrapping is the process of using a compiler to compile itself.
More accurately, it means using an older compiler to compile a newer version of the same compiler.

This raises a chicken-and-egg paradox: where did the first compiler come from?
It must have been written in a different language.
In Rust's case, it was written in OCaml.
However, it was abandoned long ago, and the
only way to build a modern version of rustc is with a slightly less modern version.

This is exactly how Rust's bootstrap build system works: it downloads the current beta release of
rustc, then uses it to compile the new compiler.

In this section, we give a high-level overview of
what Bootstrap does, followed by a high-level
introduction to how Bootstrap does it.

Additionally, see debugging bootstrap to learn about debugging methods.


Src Doc Rustc Dev Guide Src Tests Codegen Backend Tests Intro (`src/doc/rustc-dev-guide/src/tests/codegen-backend-tests/intro.md`)

Codegen backend testing

See also the Code generation chapter.

In addition to the primary LLVM codegen backend, the rust-lang/rust CI also runs tests of the cranelift and GCC codegen backends in certain test jobs.

For more details on the tests involved, see:


Src Doc Rustc Src Lints Listing Index (`src/doc/rustc/src/lints/listing/index.md`)

Lint Listing

This section lists out all of the lints, grouped by their default lint levels.

You can also see this list by running rustc -W help.


Src Tools Rust Analyzer Docs Book Src README (`src/tools/rust-analyzer/docs/book/src/README.md`)

rust-analyzer

rust-analyzer is a language server that provides IDE functionality for
writing Rust programs. You can use it with any editor that supports
the Language Server
Protocol
(VS
Code, Vim, Emacs, Zed, etc).

rust-analyzer features include go-to-definition, find-all-references,
refactorings and code completion. rust-analyzer also supports
integrated formatting (with rustfmt) and integrated diagnostics (with
rustc and clippy).

Internally, rust-analyzer is structured as a set of libraries for
analyzing Rust code. See
Architecture
for more details.

To improve this document, send a pull request:
https://github.com/rust-lang/rust-analyzer

The manual is written in markdown and includes
some extra files which are generated from the source code. Run
cargo test and cargo xtask codegen to create these.

If you have questions about using rust-analyzer, please ask them in the
"IDEs and Editors" topic of Rust
users forum.


Src Tools Rust Analyzer Docs Book Src Contributing README (`src/tools/rust-analyzer/docs/book/src/contributing/README.md`)

Contributing Quick Start

rust-analyzer is an ordinary Rust project, which is organized as a Cargo workspace, builds on stable and doesn't depend on C libraries.
So, just

bash
cargo test

should be enough to get you started!

To learn more about how rust-analyzer works, see Architecture.
It also explains the high-level layout of the source code.
Do skim through that document.

We also publish rustdoc docs to https://rust-lang.github.io/rust-analyzer/ide/.
Note though, that the internal documentation is very incomplete.

Various organizational and process issues are discussed in this document.

Getting in Touch

rust-analyzer is a part of the RLS-2.0 working
group
.
Discussion happens in this Zulip stream:

https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer

Issue Labels

  • good-first-issue
    are good issues to get into the project.
  • E-has-instructions
    issues have links to the code in question and tests.
  • Broken Window
    are issues which are not necessarily critical by themselves, but which should be fixed ASAP regardless, to avoid accumulation of technical debt.
  • E-easy,
    E-medium,
    E-hard,
    E-unknown,
    labels are estimates for how hard would be to write a fix. Each triaged issue should have one of these labels.
  • S-actionable and
    S-unactionable
    specify if there are concrete steps to resolve or advance an issue. Roughly, actionable issues need only work to be fixed,
    while unactionable ones are blocked either on user feedback (providing a reproducible example), or on larger architectural
    work or decisions. This classification is descriptive, not prescriptive, and might be wrong: Any unactionable issue might have a simple fix that we missed.
    Each triaged issue should have one of these labels.
  • fun
    is for cool, but probably hard stuff.
  • C-Architecture
    is for moderate/large scale architecture discussion.
    Also a kind of fun.
    These issues should generally include a link to a Zulip discussion thread.

Code Style & Review Process

See the Style Guide.

Cookbook

CI

We use GitHub Actions for CI.
Most of the things, including formatting, are checked by cargo test.
If cargo test passes locally, that's a good sign that CI will be green as well.
The only exception is that some long-running tests are skipped locally by default.
Use env RUN_SLOW_TESTS=1 cargo test to run the full suite.

We use bors to enforce the not rocket science rule.

Launching rust-analyzer

Debugging the language server can be tricky.
LSP is rather chatty, so driving it from the command line is not really feasible, driving it via VS Code requires interacting with two processes.

For this reason, the best way to see how rust-analyzer works is to find a relevant test and execute it.
VS Code & Emacs include an action for running a single test.

Launching a VS Code instance with a locally built language server is also possible.
There's "Run Extension (Debug Build)" launch configuration for this in VS Code.

In general, I use one of the following workflows for fixing bugs and implementing features:

If the problem concerns only internal parts of rust-analyzer (i.e. I don't need to touch the rust-analyzer crate or TypeScript code), there is a unit-test for it.
So, I use rust-analyzer: Run action in VS Code to run this single test, and then just do printf-driven development/debugging.
As a sanity check after I'm done, I use cargo xtask install --server and Reload Window action in VS Code to verify that the thing works as I expect.

If the problem concerns only the VS Code extension, I use Run Installed Extension launch configuration from launch.json.
Notably, this uses the usual rust-analyzer binary from PATH.
For this, it is important to have the following in your settings.json file:

json
{
    "rust-analyzer.server.path": "rust-analyzer"
}

After I am done with the fix, I use cargo xtask install --client to try the new extension for real.

If I need to fix something in the rust-analyzer crate, I feel sad because it's on the boundary between the two processes, and working there is slow.
I usually just cargo xtask install --server and poke changes from my live environment.
Note that this uses --release, which is usually faster overall, because loading stdlib into debug version of rust-analyzer takes a lot of time.
To speed things up, sometimes I open a temporary hello-world project which has "rust-analyzer.cargo.sysroot": null in .code/settings.json.
This flag causes rust-analyzer to skip loading the sysroot, which greatly reduces the amount of things rust-analyzer needs to do, and makes printf's more useful.
Note that you should only use the eprint! family of macros for debugging: stdout is used for LSP communication, and print! would break it.

If I need to fix something simultaneously in the server and in the client, I feel even more sad.
I don't have a specific workflow for this case.

Additionally, I use cargo run --release -p rust-analyzer -- analysis-stats path/to/some/rust/crate to run a batch analysis.
This is primarily useful for performance optimizations, or for bug minimization.

TypeScript Tests

If you change files under editors/code and would like to run the tests and linter, install npm and run:

bash
cd editors/code
npm ci
npm run lint

How to ...

  • ... add an assist? #7535
  • ... add a new protocol extension? #4569
  • ... add a new configuration option? #7451
  • ... add a new completion? #6964
  • ... allow new syntax in the parser? #7338

Logging

Logging is done by both rust-analyzer and VS Code, so it might be tricky to figure out where logs go.

Inside rust-analyzer, we use the tracing crate for logging,
and tracing-subscriber for logging frontend.
By default, log goes to stderr, but the stderr itself is processed by VS Code.
--log-file CLI argument allows logging to file.
Setting the RA_LOG_FILE= environment variable will also log to file, it will also override --log-file.

To see the server stderr output in the running VS Code instance, go to the "Output" tab of the panel
and select rust-analyzer Language Server.
This shows eprintln! as well.
Note that stdout is used by LSP messages, so using println!—or anything that writes to stdout—will break rust-analyzer!

To log all communication between the server and the client, there are two choices:

  • You can log on the server side, by running something like

    bash
    env RA_LOG=lsp_server=debug code .
  • You can log on the client side, by the rust-analyzer: Toggle LSP Logs command or enabling "rust-analyzer.trace.server": "verbose" workspace setting.
    These logs are shown in a separate tab named rust-analyzer LSP Trace in the output and could be used with LSP inspector.
    Kudos to @DJMcNab for setting this awesome infra up!

Finally there are the logs of the VSCode extension itself which go into the rust-analyzer Extension output tab.

There are also several VS Code commands which might be of interest:

  • rust-analyzer: Status shows some memory-usage statistics.

  • rust-analyzer: View Hir shows the HIR expressions within the function containing the cursor.

  • If rust-analyzer.showSyntaxTree is enabled in settings, Rust Syntax Tree: Focus on Rust Syntax Tree View shows the syntax tree of the current file.

    You can click on nodes in the rust editor to go to the corresponding syntax node.

    You can click on Reveal Syntax Element next to a syntax node to go to the corresponding rust code and highlight the proper text range.

    If you trigger Go to Definition in the inspected Rust source file,
    the syntax tree view should scroll to and select the
    appropriate syntax node token.

    You can click on Copy next to a syntax node to copy a text representation of the node.

Profiling

We have a built-in hierarchical profiler, you can enable it by using RA_PROFILE env-var:

bash
RA_PROFILE=*             // dump everything
RA_PROFILE=foo|bar|baz   // enabled only selected entries
RA_PROFILE=*@3>10        // dump everything, up to depth 3, if it takes more than 10 ms

Some rust-analyzer contributors have export RA_PROFILE='*>10' in my shell profile.

For machine-readable JSON output, we have the RA_PROFILE_JSON env variable. We support
filtering only by span name:

bash
RA_PROFILE=* // dump everything
RA_PROFILE_JSON="vfs_load|parallel_prime_caches|discover_command" // dump selected spans

We also have a "counting" profiler which counts number of instances of popular structs.
It is enabled by RA_COUNT=1.

To measure time for from-scratch analysis, use something like this:

bash
cargo run --release -p rust-analyzer -- analysis-stats ../chalk/

Look for fn benchmark_xxx tests for a quick way to reproduce performance problems.

Release Process

Release process is handled by release, dist, publish-release-notes and promote xtasks, release being the main one.

release assumes that you have checkouts of rust-analyzer, rust-analyzer.github.io, and rust-lang/rust in the same directory:

bash
./rust-analyzer
./rust-analyzer.github.io
./rust-rust-analyzer  # Note the name!

The remote for rust-analyzer must be called upstream (I use origin to point to my fork).
In addition, for xtask promote (see below), rust-rust-analyzer must have a rust-analyzer remote pointing to this repository on GitHub.

release calls the GitHub API calls to scrape pull request comments and categorize them in the changelog.
This step uses the curl and jq applications, which need to be available in PATH.
Finally, you need to obtain a GitHub personal access token and set the GITHUB_TOKEN environment variable.

Release steps:

  1. Set the GITHUB_TOKEN environment variable.
  2. Inside rust-analyzer, run cargo xtask release. This will:
    • checkout the release branch
    • reset it to upstream/nightly
    • push it to upstream. This triggers GitHub Actions which:
      • runs cargo xtask dist to package binaries and VS Code extension
      • makes a GitHub release
      • publishes the VS Code extension to the marketplace
    • call the GitHub API for PR details
    • create a new changelog in rust-analyzer.github.io
  3. While the release is in progress, fill in the changelog.
  4. Commit & push the changelog.
  5. Run cargo xtask publish-release-notes <CHANGELOG> -- this will convert the changelog entry in AsciiDoc to Markdown and update the body of GitHub Releases entry.
  6. Tweet.
  7. Perform a subtree pull.
  8. Perform a subtree push.

If the GitHub Actions release fails because of a transient problem like a timeout, you can re-run the job from the Actions console.
If it fails because of something that needs to be fixed, remove the release tag (if needed), fix the problem, then start over.
Make sure to remove the new changelog post created when running cargo xtask release a second time.

We release "nightly" every night automatically and promote the latest nightly to "stable" manually, every week.

We don't do "patch" releases, unless something truly egregious comes up.
To do a patch release, cherry-pick the fix on top of the current release branch and push the branch.
There's no need to write a changelog for a patch release, it's OK to include the notes about the fix into the next weekly one.
Note: we tag releases by dates, releasing a patch release on the same day should work (by overwriting a tag), but I am not 100% sure.

Permissions

There are two sets of people with extra permissions:

  • The rust-lang team t-rust-analyzer.
    This team has write access to the repository and merge queue permissions (note the repo itself is managed by infra admins).
    It's ok to self-approve if you think you know what you are doing!
    Feel free to request a review or assign any PR to a reviewer with the relevant expertise to bring the work to their attention.
    Don't feel pressured to review assigned PRs though.
    If you don't feel like reviewing for whatever reason, someone else will pick the review up (but please speak up if you don't feel like it)!
  • The rust-lang team t-rust-analyzer-contributors.
    This team has general triaging permissions allowing to label, close and re-open issues.

Synchronizing subtree changes

rust-analyzer is a josh subtree of the rust-lang/rust
repository. We use the rustc-josh-sync tool to perform synchronization between these two
repositories. You can find documentation of the tool here.

You can install the synchronization tool using the following commands:

bash
cargo install --locked --git https://github.com/rust-lang/josh-sync

Both pulls (synchronizing changes from rust-lang/rust into rust-analyzer) and pushes (synchronizing
changes from rust-analyzer into rust-lang/rust) are performed from this repository.
changes from rust-analyzer to rust-lang/rust) are performed from this repository.

Usually we first perform a pull, wait for it to be merged, and then perform a push.

Performing a pull

  1. Checkout a new branch that will be used to create a PR against rust-analyzer
  2. Run the pull command
    text
    rustc-josh-sync pull
  3. Push the branch to your fork of rust-analyzer and create a PR
  • If you have the gh CLI installed, rustc-josh-sync can create the PR for you.

Performing a push

Wait for the previous pull to be merged.

  1. Switch to master and pull
  2. Run the push command to create a branch named `` in a rustc fork under the <gh-username> account
    text
    rustc-josh-sync push <branch-name> <gh-username>
    • The push will ask you to download a checkout of the rust-lang/rust repository.
    • If you get prompted for a password, see this.
  3. Create a PR from `` into rust-lang/rust

Besides the rust checkout, the Josh cache (stored under ~/.cache/rustc-josh) will contain a bare clone of rust-lang/rust. This currently takes several GBs.


Src Doc Complement Design Faq (`src/doc/complement-design-faq.md`)

% The Rust Design FAQ

This content has moved to the website.


Src Doc Complement Lang Faq (`src/doc/complement-lang-faq.md`)

% The Rust Language FAQ

This content has moved to the website.


Src Doc Complement Project Faq (`src/doc/complement-project-faq.md`)

% The Rust Project FAQ

This content has moved to the website.


Src Doc Grammar (`src/doc/grammar.md`)

% Grammar

The Rust grammar may now be found in the reference. Additionally, the grammar
working group
is working on producing a testable grammar.


Src Doc Guide Crates (`src/doc/guide-crates.md`)

% The (old) Rust Crates and Modules Guide

This content has moved into
the Rust Programming Language book.


Src Doc Guide Error Handling (`src/doc/guide-error-handling.md`)

% Error Handling in Rust

This content has moved into
the Rust Programming Language book.


Src Doc Guide Ffi (`src/doc/guide-ffi.md`)

% The (old) Rust Foreign Function Interface Guide

This content has moved into
the Rust Programming Language book.


Src Doc Guide Macros (`src/doc/guide-macros.md`)

% The (old) Rust Macros Guide

This content has moved into
the Rust Programming Language book.


Src Doc Guide Ownership (`src/doc/guide-ownership.md`)

% The (old) Rust Ownership Guide

This content has moved into
the Rust Programming Language book.


Src Doc Guide Pointers (`src/doc/guide-pointers.md`)

% The Rust Pointer Guide

This content has been removed, with no direct replacement. Rust only
has two built-in pointer types now,
references and raw
pointers
. Older Rusts had many more pointer
types, they’re gone now.


Src Doc Guide Strings (`src/doc/guide-strings.md`)

% The (old) Guide to Rust Strings

This content has moved into
the Rust Programming Language book.


Src Doc Guide Tasks (`src/doc/guide-tasks.md`)

% The (old) Rust Threads and Communication Guide

This content has moved into
the Rust Programming Language book.


Src Doc Guide Testing (`src/doc/guide-testing.md`)

% The (old) Rust Testing Guide

This content has moved into
the Rust Programming Language book.


Src Doc Guide Unsafe (`src/doc/guide-unsafe.md`)

% Writing Safe Low-level and Unsafe Code in Rust

This content has moved into
the Rust Programming Language book.


Src Doc Guide (`src/doc/guide.md`)

% The (old) Rust Guide

This content has moved into
the Rust Programming Language book.


Src Doc Not Found (`src/doc/not_found.md`)

% Not Found

Looks like you've taken a wrong turn.

Some things that might be helpful to you though:

text
<fieldset id="search-from" style="display:none">
  <label> From the Standard Library</label>
  <label> From DuckDuckGo</label>
</fieldset>

Reference

Docs

The standard library


Src Doc Reference (`src/doc/reference.md`)

% The Rust Reference has moved

We've split up the reference into chapters. Please find it at its new
home here.


Src Doc Robots (`src/doc/robots.txt`)

User-agent: *
Disallow: /1.
Disallow: /0.
Disallow: /book/first-edition/
Disallow: /book/second-edition/
Disallow: /stable/book/first-edition/
Disallow: /stable/book/second-edition/
Disallow: /beta/book/first-edition/
Disallow: /beta/book/second-edition/
Disallow: /nightly/book/first-edition/
Disallow: /nightly/book/second-edition/

Sitemap: https://doc.rust-lang.org/sitemap.txt


Src Doc Rust (`src/doc/rust.md`)

% The Rust Reference Manual

The manual has moved, and is now called the reference.


Src Doc Rustdoc (`src/doc/rustdoc.md`)

% Rust Documentation

This has been moved into the rustdoc book.


Src Doc Sitemap (`src/doc/sitemap.txt`)

https://doc.rust-lang.org/stable/
https://doc.rust-lang.org/beta/
https://doc.rust-lang.org/nightly/


Src Doc Tutorial (`src/doc/tutorial.md`)

% The Rust Tutorial

This tutorial has been deprecated in favor of the Book, which is available free online and in dead tree form. Go check that out instead!


Compiler Rustc Codegen Cranelift Docs Dwarf (`compiler/rustc_codegen_cranelift/docs/dwarf.md`)

Line number information

Line number information maps between machine code instructions and the source level location.

Encoding

The line number information is stored in the .debug_line section for ELF and __debug_line
section of the __DWARF segment for Mach-O object files. The line number information contains a
header followed by the line program. The line program is a program for a virtual machine with
instructions like set line number for the current machine code instruction and advance the current
machine code instruction.

Tips

You need to set either DW_AT_low_pc and DW_AT_high_pc or DW_AT_ranges of a
DW_TAG_compilation_unit to the range of addresses in the compilation unit. After that you need
to set DW_AT_stmt_list to the .debug_line section offset of the line program. Otherwise a
debugger won't find the line number information. On macOS the debuginfo relocations must be
section relative and not symbol relative.
See #303 (comment)
for more information.

Function debuginfo

Tips

DW_TAG_subprogram requires DW_AT_name, DW_AT_low_pc and DW_AT_high_pc or DW_AT_ranges.
Otherwise gdb will silently skip it. When DW_AT_high_pc is a length instead of an address, the
DWARF version must be at least 4.

IRC log of #gdb on irc.freenode.org at 2020-04-23

text
/* Detailed source-code truncated for AI context efficiency. */

Compiler Rustc Codegen Cranelift Docs Rustc Testing (`compiler/rustc_codegen_cranelift/docs/rustc_testing.md`)

Building and testing with changes in rustc code

This is useful when changing code in rustc_codegen_cranelift as part of changing main Rust repository.
This can happen, for example, when you are implementing a new compiler intrinsic.

Instruction below uses $RustCheckoutDir as substitute for any folder where you cloned Rust repository.

You need to do this steps to successfully compile and use the cranelift backend with your changes in rustc code:

  1. cd $RustCheckoutDir
  2. Run python x.py setup and choose option for compiler (b).
  3. Build compiler and necessary tools: python x.py build --stage=2 compiler library/std src/tools/rustdoc src/tools/rustfmt
    • (Optional) You can also build cargo by adding src/tools/cargo to previous command.
  4. Copy cargo from a nightly toolchain: cp $(rustup +nightly which cargo) ./build/host/stage2/bin/cargo. Note that you would need to do this every time you rebuilt rust repository.
  5. Link your new rustc to toolchain: rustup toolchain link stage2 ./build/host/stage2/.
  6. (Windows only) compile the build system: rustc +stage2 -O build_system/main.rs -o y.exe.
  7. You need to prefix every ./y.sh (or y if you built build_system/main.rs as y) command by rustup run stage2 to make cg_clif use your local changes in rustc.
  • rustup run stage2 ./y.sh prepare
  • rustup run stage2 ./y.sh build
  • (Optional) run tests: rustup run stage2 ./y.sh test
  1. Now you can use your cg_clif build to compile other Rust programs, e.g. you can open any Rust crate and run commands like $RustCheckoutDir/compiler/rustc_codegen_cranelift/dist/cargo-clif build --release.

You can also set rust-analyzer.rustc.source to your rust workspace to get rust-analyzer to understand your changes.


Compiler Rustc Codegen Cranelift Docs Usage (`compiler/rustc_codegen_cranelift/docs/usage.md`)

Usage

rustc_codegen_cranelift can be used as a near-drop-in replacement for cargo build or cargo run for existing projects.

Assuming $cg_clif_dir is the directory you cloned this repo into and you followed the instructions (y.sh prepare and y.sh build or test.sh).

Cargo

In the directory with your project (where you can do the usual cargo build), run:

bash
$ $cg_clif_dir/dist/cargo-clif build

This will build your project with rustc_codegen_cranelift instead of the usual LLVM backend.

Rustc

You should prefer using the Cargo method.

bash
$ $cg_clif_dir/dist/rustc-clif my_crate.rs

Jit mode

⚠⚠⚠ The JIT mode is highly experimental. It may be slower than AOT compilation due to lack of incremental compilation. It may also be hard to setup if you have cargo dependencies. ⚠⚠⚠

In jit mode cg_clif will immediately execute your code without creating an executable file.

This requires all dependencies to be available as dynamic library.
The jit mode will probably need cargo integration to make this possible.

bash
$ $cg_clif_dir/dist/cargo-clif jit

or

bash
$ $cg_clif_dir/dist/rustc-clif -Cllvm-args=jit-mode -Cprefer-dynamic my_crate.rs

Shell

These are a few functions that allow you to easily run rust code from the shell using cg_clif as jit.

bash
function jit_naked() {
    echo "$@" | $cg_clif_dir/dist/rustc-clif - -Zunstable-options -Cllvm-args=jit-mode -Cprefer-dynamic
}

function jit() {
    jit_naked "fn main() { $@ }"
}

function jit_calc() {
    jit 'println!("0x{:x}", ' $@ ');';
}

Compiler Rustc Codegen Gcc Doc Add Attribute (`compiler/rustc_codegen_gcc/doc/add-attribute.md`)

Add support for a new function attribute

To add support for a new function attribute in libgccjit, you need to do the following steps:

  1. Copy the corresponding function from c-family/c-attribs.cc into jit/dummy-frontend.cc. For example if you add the target attribute, the function name will be handle_target_attribute.
  2. Copy the corresponding entry from the c_common_attribute_table variable in the c-family/c-attribs.cc file into the jit_attribute_table variable in jit/dummy-frontend.cc.
  3. Add a new variant in the gcc_jit_fn_attribute enum in the jit/libgccjit.h file.
  4. Add a test to ensure the attribute is correctly applied in gcc/testsuite/jit.dg/. Take a look at gcc/testsuite/jit.dg/test-nonnull.c if you want an example.
  5. Run the example like this (in your gcc-build folder): make check-jit RUNTESTFLAGS="-v -v -v jit.exp=jit.dg/test-nonnull.c"

Once done, you need to update the gccjit.rs crate to add the new enum variant in the corresponding enum (FnAttribute).

Finally, you need to update this repository by calling the relevant API you added in gccjit.rs.

To test it, build gcc, run cargo update -p gccjit and then you can test the generated output for a given Rust crate.


Compiler Rustc Codegen Gcc Doc Debugging Libgccjit (`compiler/rustc_codegen_gcc/doc/debugging-libgccjit.md`)

Debugging libgccjit

Sometimes, libgccjit will crash and output an error like this:

text
during RTL pass: expand
libgccjit.so: error: in expmed_mode_index, at expmed.h:249
0x7f0da2e61a35 expmed_mode_index
	../../../gcc/gcc/expmed.h:249
0x7f0da2e61aa4 expmed_op_cost_ptr
	../../../gcc/gcc/expmed.h:271
0x7f0da2e620dc sdiv_cost_ptr
	../../../gcc/gcc/expmed.h:540
0x7f0da2e62129 sdiv_cost
	../../../gcc/gcc/expmed.h:558
0x7f0da2e73c12 expand_divmod(int, tree_code, machine_mode, rtx_def*, rtx_def*, rtx_def*, int)
	../../../gcc/gcc/expmed.c:4335
0x7f0da2ea1423 expand_expr_real_2(separate_ops*, rtx_def*, machine_mode, expand_modifier)
	../../../gcc/gcc/expr.c:9240
0x7f0da2cd1a1e expand_gimple_stmt_1
	../../../gcc/gcc/cfgexpand.c:3796
0x7f0da2cd1c30 expand_gimple_stmt
	../../../gcc/gcc/cfgexpand.c:3857
0x7f0da2cd90a9 expand_gimple_basic_block
	../../../gcc/gcc/cfgexpand.c:5898
0x7f0da2cdade8 execute
	../../../gcc/gcc/cfgexpand.c:6582

To see the code which causes this error, call the following function:

c
gcc_jit_context_dump_to_file(ctxt, "/tmp/output.c", 1 /* update_locations */)

This will create a C-like file and add the locations into the IR pointing to this C file.
Then, rerun the program and it will output the location in the second line:

text
libgccjit.so: /tmp/something.c:61322:0: error: in expmed_mode_index, at expmed.h:249

Or add a breakpoint to add_error in gdb and print the line number using:

text
p loc->m_line
p loc->m_filename->m_buffer

To print a debug representation of a tree:

c
debug_tree(expr);

(defined in print-tree.h)

To print a debug representation of a gimple struct:

c
debug_gimple_stmt(gimple_struct)

To get the rustc command to run in gdb, add the --verbose flag to cargo build.

To have the correct file paths in gdb instead of /usr/src/debug/gcc/libstdc++-v3/libsupc++/eh_personality.cc:

Maybe by calling the following at the beginning of gdb:

text
set substitute-path /usr/src/debug/gcc /path/to/gcc-repo/gcc

FIXME(antoyo): but that's not what I remember I was doing.


Compiler Rustc Codegen Gcc Doc Debugging (`compiler/rustc_codegen_gcc/doc/debugging.md`)

Debugging

How to debug GCC LTO

Run the command with -v -save-temps and then extract the lto1 line from the output and run that under the debugger.

How to debug stdarch tests that cannot be run locally

First, run the tests normally:


cd build/build_sysroot/sysroot_src/library/stdarch/ STDARCH_TEST_EVERYTHING=1 CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER="sde -future -rtm_mode full --" TARGET=x86_64-unknown-linux-gnu ../../../../../y.sh cargo test

It will show the command it ran, something like this:


process didn't exit successfully: `sde -future -rtm_mode full -- /home/user/projects/rustc_codegen_gcc/build/build_sysroot/sysroot_src/library/stdarch/target/debug/deps/core_arch-fd2d75f89baae5c6` (signal: 11, SIGSEGV: invalid memory reference)

Then add the -debug flag to it:


sde -debug -future -rtm_mode full -- /home/user/projects/rustc_codegen_gcc/build/build_sysroot/sysroot_src/library/stdarch/target/debug/deps/core_arch-fd2d75f89baae5c6

To see the symbols in gdb, specify the executable in your command:


gdb /home/user/projects/rustc_codegen_gcc/build/build_sysroot/sysroot_src/library/stdarch/target/debug/deps/core_arch-fd2d75f89baae5c6

and then write the gdb command that sde tells you to use, something like:


target remote :51299


Compiler Rustc Codegen Gcc Doc Errors (`compiler/rustc_codegen_gcc/doc/errors.md`)

Common errors

This file lists errors that were encountered and how to fix them.

`failed to build archive` error

When you get this error:

text
error: failed to build archive: failed to open object file: No such file or directory (os error 2)

That can be caused by the fact that you try to compile with lto = "fat", but you didn't compile the sysroot with LTO.
(Not sure if that's the reason since I cannot reproduce anymore. Maybe it happened when forgetting setting FAT_LTO.)

ld: cannot find crtbegin.o

When compiling an executable with libgccjit, if setting the *LIBRARY_PATH variables to the install directory, you will get the following errors:

text
ld: cannot find crtbegin.o: No such file or directory
ld: cannot find -lgcc: No such file or directory
ld: cannot find -lgcc: No such file or directory
libgccjit.so: error: error invoking gcc driver

To fix this, set the variables to gcc-build/build/gcc.


Compiler Rustc Codegen Gcc Doc Gimple (`compiler/rustc_codegen_gcc/doc/gimple.md`)

GIMPLE

You can see the full documentation about what GIMPLE is here. In this document we will explain how to generate it.

First, we'll copy the content from gcc/gcc/testsuite/jit.dg/test-const-attribute.c into a
file named local.c and remove the content we're not interested in:

diff
- /* { dg-do compile { target x86_64-*-* } } */
...
- /* We don't want set_options() in harness.h to set -O3 to see that the const
-    attribute affects the optimizations. */
- #define TEST_ESCHEWS_SET_OPTIONS
- static void set_options (gcc_jit_context *ctxt, const char *argv0)
- {
-   // Set "-O3".
-   gcc_jit_context_set_int_option(ctxt, GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL, 3);
- }
-
- #define TEST_COMPILING_TO_FILE
- #define OUTPUT_KIND      GCC_JIT_OUTPUT_KIND_ASSEMBLER
- #define OUTPUT_FILENAME  "output-of-test-const-attribute.c.s"
- #include "harness.h"
...
- /* { dg-final { jit-verify-output-file-was-created "" } } */
- /* Check that the loop was optimized away */
- /* { dg-final { jit-verify-assembler-output-not "jne" } } */

Then we'll add a main function which will call the create_code function but
also add the calls we need to generate the GIMPLE:

c
int main() {
    gcc_jit_context *ctxt = gcc_jit_context_acquire();
    // To set `-O3`, update it depending on your needs.
    gcc_jit_context_set_int_option(ctxt, GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL, 3);
    // Very important option to generate the gimple format.
    gcc_jit_context_set_bool_option(ctxt, GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE, 1);
    create_code(ctxt, NULL);

    gcc_jit_context_compile(ctxt);
    // If you want to compile to assembly (or any other format) directly, you can
    // use the following call instead:
    // gcc_jit_context_compile_to_file(ctxt, GCC_JIT_OUTPUT_KIND_ASSEMBLER, "out.s");

    return 0;
}

Then we can compile it by using:

console
gcc local.c -I `pwd`/gcc/gcc/jit/ -L `pwd`/gcc-build/gcc -lgccjit -o out

And finally when you run it:

console
LD_LIBRARY_PATH=`pwd`/gcc-build/gcc LIBRARY_PATH=`pwd`/gcc-build/gcc ./out

It should display:

c
__attribute__((const))
int xxx ()
{
  int D.3394;
  int sum;
  int x;

  <D.3377>:
  x = 45;
  sum = 0;
  goto loop_cond;
  loop_cond:
  x = x >> 1;
  if (x != 0) goto after_loop; else goto loop_body;
  loop_body:
  _1 = foo (x);
  _2 = _1 * 2;
  x = x + _2;
  goto loop_cond;
  after_loop:
  D.3394 = sum;
  return D.3394;
}

An alternative way to generate the GIMPLE is to replace:

c
gcc_jit_context_set_bool_option(ctxt, GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE, 1);

with:

c
gcc_jit_context_add_command_line_option(ctxt, "-fdump-tree-gimple");

(although you can have both at the same time too). Then you can compile it like previously. Only one difference: before executing it, I recommend to run:

console
rm -rf /tmp/libgccjit-*

to make it easier for you to know which folder to look into.

Once the execution is done, you should now have a file with path looking like /tmp/libgccjit-9OFqkD/fake.c.006t.gimple which contains the GIMPLE format.


Compiler Rustc Codegen Gcc Doc Sending Gcc Patch (`compiler/rustc_codegen_gcc/doc/sending-gcc-patch.md`)

This guide explains what to do to send a GCC patch for review.

All the commands are supposed to be run in the folder where you cloned GCC.

bash
./contrib/gcc-changelog/git_check_commit.py

You can provide a specific commit hash:

bash
./contrib/gcc-changelog/git_check_commit.py abdef78989

a range:

bash
./contrib/gcc-changelog/git_check_commit.py HEAD~2

or even a comparison with a remote branch:

bash
./contrib/gcc-changelog/git_check_commit.py upstream/master..HEAD

When there is no more errors, generate the git patch:

bash
git format-patch -1 `git rev-parse --short HEAD`

Then you can run the remaining checks using:

bash
contrib/check_GNU_style.sh 0001-your-patch.patch

When you have no more errors, you can send the .patch file to GCC by sending an
email to [email protected] and to the relevant GCC mailing lists
depending on what your patch changes. You can find the list of the mailing lists
here.

You can find more information about "contributing to GCC" here.


Compiler Rustc Codegen Gcc Doc Subtree (`compiler/rustc_codegen_gcc/doc/subtree.md`)

git subtree sync

rustc_codegen_gcc is a subtree of the rust compiler. As such, it needs to be
synced from time to time to ensure changes that happened on their side are also
included on our side.

How to install a forked git-subtree

Using git-subtree with rustc requires a patched git to make it work.
The PR that is needed is here.
Use the following instructions to install it:

bash
git clone [email protected]:tqc/git.git
cd git
git checkout tqc/subtree
make
make install
cd contrib/subtree
make
cp git-subtree ~/bin

Syncing with rust compiler

Do a sync with this command:

bash
PATH="$HOME/bin:$PATH" ~/bin/git-subtree push -P compiler/rustc_codegen_gcc/ ../rustc_codegen_gcc/ sync_branch_name
cd ../rustc_codegen_gcc
git checkout master
git pull
git checkout sync_branch_name
git merge master

To send the changes to the rust repo:

bash
cd ../rust
git pull origin master
git checkout -b subtree-update_cg_gcc_YYYY-MM-DD
PATH="$HOME/bin:$PATH" ~/bin/git-subtree pull --prefix=compiler/rustc_codegen_gcc/ https://github.com/rust-lang/rustc_codegen_gcc.git master
# Don't forget to update the `gcc` submodule to the same version as the
# one in `rustc_codegen_gcc/libgccjit.version`.
git push

# Immediately merge the merge commit into cg_gcc to prevent merge conflicts when syncing from rust-lang/rust later.
PATH="$HOME/bin:$PATH" ~/bin/git-subtree push -P compiler/rustc_codegen_gcc/ ../rustc_codegen_gcc/ sync_branch_name

FIXME: write a script that does the above.

https://rust-lang.zulipchat.com/#narrow/stream/301329-t-devtools/topic/subtree.20madness/near/258877725


Compiler Rustc Codegen Gcc Doc Tests (`compiler/rustc_codegen_gcc/doc/tests.md`)

Tests

Show the rustc command for UI tests

Add --test-args "--verbose" to ./x.py test.


Compiler Rustc Codegen Gcc Doc Tips (`compiler/rustc_codegen_gcc/doc/tips.md`)

Tips

The following shows how to do different random small things we encountered and thought could
be useful.

How to send arguments to the GCC linker

text
CG_RUSTFLAGS="-Clink-args=-save-temps -v" ../y.sh cargo build

How to send arguments to GCC

The -Cllvm-args rustc flag is repurposed by rustc_codegen_gcc to pass arguments directly to the GCC backend. You can use it via the CG_RUSTFLAGS environment variable. For example, to pass a -f flag to GCC:

text
CG_RUSTFLAGS="-Cllvm-args=-fflag-name" ../y.sh cargo build

How to see the personality functions in the asm dump

text
CG_RUSTFLAGS="-Clink-arg=-save-temps -v -Clink-arg=-dA" ../y.sh cargo build

How to see the LLVM IR for a sysroot crate

text
cargo build -v --target x86_64-unknown-linux-gnu -Zbuild-std
# Take the command from the output and add --emit=llvm-ir

To prevent the linker from unmangling symbols

Run with:

text
COLLECT_NO_DEMANGLE=1

How to use a custom-build rustc

  • Build the stage2 compiler (rustup toolchain link debug-current build/x86_64-unknown-linux-gnu/stage2).
  • Clean and rebuild the codegen with debug-current in the file rust-toolchain.toml.

How to use a custom sysroot source path

If you wish to build a custom sysroot, pass the path of your sysroot source to --sysroot-source during the prepare step, like so:

text
./y.sh prepare --sysroot-source /path/to/custom/source

How to use [mem-trace](https://github.com/antoyo/mem-trace)

rustc needs to be built without jemalloc so that mem-trace can overload malloc since jemalloc is linked statically, so a LD_PRELOAD-ed library won't have a chance to intercept the calls to malloc.

How to generate GIMPLE

If you need to check what gccjit is generating (GIMPLE), then take a look at how to
generate it in gimple.md.

How to build a cross-compiling libgccjit

Building libgccjit

Configuring rustc_codegen_gcc

  • Run ./y.sh prepare --cross so that the sysroot is patched for the cross-compiling case.
  • Set the path to the cross-compiling libgccjit in gcc-path (in config.toml).
  • Make sure you have the linker for your target (for instance m68k-unknown-linux-gnu-gcc) in your $PATH. You can specify which linker to use via CG_RUSTFLAGS="-Clinker=<linker>", for instance: CG_RUSTFLAGS="-Clinker=m68k-unknown-linux-gnu-gcc". Specify the target when building the sysroot: ./y.sh build --sysroot --target-triple m68k-unknown-linux-gnu.
  • Build your project by specifying the target and the linker to use: CG_RUSTFLAGS="-Clinker=m68k-unknown-linux-gnu-gcc" ../y.sh cargo build --target m68k-unknown-linux-gnu.

If the target is not yet supported by the Rust compiler, create a target specification file (note that the arch specified in this file must be supported by the rust compiler).
Then, you can use it the following way:

  • Add the target specification file using --target as an absolute path to build the sysroot: ./y.sh build --sysroot --target-triple m68k-unknown-linux-gnu --target $(pwd)/m68k-unknown-linux-gnu.json
  • Build your project by specifying the target specification file: ../y.sh cargo build --target path/to/m68k-unknown-linux-gnu.json.

If you get the following error:

text
/usr/bin/ld: unrecognised emulation mode: m68kelf

Make sure you set gcc-path (in config.toml) to the install directory.


Src Doc Rustc Dev Guide CODE OF CONDUCT (`src/doc/rustc-dev-guide/CODE_OF_CONDUCT.md`)

The Rust Code of Conduct

This repository follows the Code of Conduct of the Rust organization.


Src Doc Rustc Dev Guide Src SUMMARY (`src/doc/rustc-dev-guide/src/SUMMARY.md`)

Summary

Getting Started

About this guide


Building and debugging `rustc`

Contributing to Rust

Bootstrapping

High-level compiler architecture

Source code representation

Supporting infrastructure

Analysis

MIR to binaries


Appendix A: Background topics

Appendix B: Glossary

Appendix C: Code Index

Appendix D: Compiler Lecture Series

Appendix E: Bibliography

Appendix F: Reading club sessions

Appendix Z: HumorRust


Src Doc Rustc Dev Guide Src About This Guide (`src/doc/rustc-dev-guide/src/about-this-guide.md`)

About this guide

This guide is meant to help document how rustc – the Rust compiler – works,
as well as to help new contributors get involved in rustc development.

Note that this guide is intended as a reference; it's recommended that you search
for the docs you're looking for instead of reading them top to bottom.

There are several parts to this guide:

  1. Building and debugging rustc:
    Contains information that should be useful no matter how you are contributing,
    about building, debugging, profiling, etc.
  2. Contributing to Rust:
    Contains information that should be useful no matter how you are contributing,
    about procedures for contribution, using git and GitHub, stabilizing features, etc.
  3. Bootstrapping:
    Describes how the Rust compiler builds itself using previous versions, including
    an introduction to the bootstrap process and debugging methods.
  4. High-level compiler architecture:
    Discusses the high-level architecture of the compiler and stages of the compile process.
  5. Source code representation:
    Describes the process of taking raw source code from the user
    and transforming it into various forms that the compiler can work with easily.
  6. Supporting infrastructure:
    Covers command-line argument conventions, compiler entry points like rustc_driver and
    rustc_interface, and the design and implementation of errors and lints.
  7. Analysis:
    Discusses the analyses that the compiler uses to check various properties of the code
    and inform later stages of the compile process (e.g., type checking).
  8. Mir to binaries: How linked executable machine code is generated.
  9. Appendices at the end with useful reference information.
    There are a few of these with different information, including a glossary.

Constant change

Keep in mind that rustc is a real production-quality product,
being worked upon continuously by a sizeable set of contributors.
As such, it has its fair share of codebase churn and technical debt.
In addition, many of the ideas discussed throughout this guide are idealized designs
that are not fully realized yet.
All this makes keeping this guide completely up to date on everything very hard!

The guide itself is of course open source as well,
and the sources are hosted on a GitHub repository.
If you find any mistakes in the guide, please file an issue.
Even better, open a PR with a correction!

If you do contribute to the guide,
please see the corresponding subsection on writing documentation in this guide.

“‘All conditioned things are impermanent’ —
when one sees this with wisdom, one turns away from suffering.”
The Dhammapada, verse 277

Other places to find information

This guide, the one you are currently reading,
contains information about how various parts of the compiler work,
and how to contribute to the compiler.

You might also find the following sites useful:

  • rustc API docs -- rustdoc documentation for the compiler, devtools, and internal tools
  • Forge -- contains documentation about Rust infrastructure, team procedures, and more
  • rust-lang/rust's LLM policy
  • compiler-team -- the home-base for the Rust compiler team, with description
    of the team procedures, active working groups, and the team calendar.
  • std-dev-guide -- a similar guide for developing the standard library.
  • rust-analyzer book -- documentation for the rust-analyzer.
  • The t-compiler Zulip
  • The Rust Internals forum, a place to ask questions and discuss Rust's internals
  • The Rust reference, even though it doesn't specifically talk about
    Rust's internals, is a great resource nonetheless
  • Although out of date, Tom Lee's great blog article is very helpful
  • The Rust Compiler Testing Docs
  • For @bors, this cheat sheet is helpful
  • Google is always helpful when programming.
    You can search all Rust documentation (the standard library,
    the compiler, the books, the references, and the guides) to quickly find
    information about the language and compiler.
  • You can also use Rustdoc's built-in search feature to find documentation on
    types and functions within the crates you're looking at.
    You can also search by type signature!
    For example, searching for * -> vec should find all functions that return a Vec<T>.
    Hint: Find more tips and keyboard shortcuts by typing ? on any Rustdoc page!

Src Doc Rustc Dev Guide Src Ambig Unambig Ty And Consts (`src/doc/rustc-dev-guide/src/ambig-unambig-ty-and-consts.md`)

Ambig/Unambig Types and Consts

Types and Consts args in the AST/HIR can be in two kinds of positions ambiguous (ambig) or unambiguous (unambig).
Ambig positions are where it would be valid to parse either a type or a const.
Unambig positions are where only one kind would be valid to parse.

rust
fn func<T, const N: usize>(arg: T) {
    //                          ^ Unambig type position
    let a: _ = arg;
    //     ^ Unambig type position

    func::<T, N>(arg);
    //     ^  ^
    //     ^^^^ Ambig position

    let _: [u8; 10];
    //      ^^  ^^ Unambig const position
    //      ^^ Unambig type position
}

Most types/consts in ambig positions are able to be disambiguated as either a type or const during parsing.
The only exceptions to this are paths and inferred generic arguments.

Paths

rust
struct Foo<const N: usize>;

fn foo<const N: usize>(_: Foo<N>) {}

At parse time we parse all unbraced generic arguments as types (ie they wind up as ast::GenericArg::Ty).
In the above example this means we would parse the generic argument to Foo as an ast::GenericArg::Ty wrapping a ast::Ty::Path(N).

Then during name resolution:

  • When encountering a single segment path with no generic arguments in generic argument position, we will first try to resolve it in the type namespace and if that fails we then attempt to resolve in the value namespace.
  • All other kinds of paths we only try to resolve in the type namespace

See LateResolutionVisitor::visit_generic_arg for where this is implemented.

Finally during AST lowering when we attempt to lower a type argument, we first check if it is a Ty::Path and if it resolved to something in the value namespace.
If it did, then we create an anon const and lower to a const argument instead of a type argument.

See LoweringContext::lower_generic_arg for where this is implemented.

Note that the ambiguity for paths is not propgated into the HIR; there's no hir::GenericArg::Path which is turned into either a Ty or Const during HIR ty lowering (though we could do such a thing).

Inferred arguments (`_`)

rust
struct Foo<const N: usize>;

fn foo() {
    let _unused: Foo<_>;
}

The only generic arguments which remain ambiguous after lowering are inferred generic arguments (_) in path segments.
In the above example,
it is not clear at parse time whether the _ argument to Foo is an inferred type argument,
or an inferred const argument.

In ambig AST positions, inferred arguments are parsed as an ast::GenericArg::Ty wrapping a ast::Ty::Infer.
Then, during AST lowering, when lowering an ast::GenericArg::Ty,
we check if it is an inferred type, and if so, lower to a hir::GenericArg::Infer.

In unambig AST positions, inferred arguments are parsed as either ast::Ty::Infer or ast::AnonConst.
The AnonConst case is quite strange;
we use ast::ExprKind::Underscore to represent the "body" of the "anon const",
although in reality we do not actually lower this to an anon const in the HIR.

It may be worth seeing if we can refactor the AST to have ast::GenericArg::Infer and then get rid of this overloaded meaning of AnonConst,
as well as the reuse of ast::Ty::Infer in ambig positions.

In unambig AST positions, during AST lowering we lower inferred arguments to hir::TyKind::Infer or hir::ConstArgKind::Infer depending on whether it is a type or const position respectively.
In ambig AST positions, during AST lowering we lower inferred arguments to hir::GenericArg::Infer.
See LoweringContext::lower_generic_arg for where this is implemented.

A naive implementation of this would result in there being potentially 5 places where you might think an inferred type/const could be found in the HIR from looking at the structure of the HIR:

  1. In unambig type position as a TyKind::Infer
  2. In unambig const arg position as a ConstArgKind::Infer
  3. In an ambig position as a GenericArg::Type(TyKind::Infer)
  4. In an ambig position as a GenericArg::Const(ConstArgKind::Infer)
  5. In an ambig position as a GenericArg::Infer

Note that places 3 and 4 would never actually be possible to encounter as we always lower to GenericArg::Infer in generic arg position.

This has a few failure modes:

  • People may write visitors which check for GenericArg::Infer but forget to check for hir::TyKind/ConstArgKind::Infer, only handling infers in ambig positions by accident.
  • People may write visitors which check for TyKind/ConstArgKind::Infer but forget to check for GenericArg::Infer, only handling infers in unambig positions by accident.
  • People may write visitors which check for GenericArg::Type/Const(TyKind/ConstArgKind::Infer) and GenericArg::Infer, not realising that we never represent inferred types/consts in ambig positions as a GenericArg::Type/Const.
  • People may write visitors which check for only TyKind::Infer and not ConstArgKind::Infer forgetting that there are also inferred const arguments (and vice versa).

To make writing HIR visitors less error prone when caring about inferred types/consts we have a relatively complex system:

  1. We have different types in the compiler for when a type or const is in an unambig or ambig position, Ty<AmbigArg> and Ty<()>.
    AmbigArg is an uninhabited type which we use in the Infer variant of TyKind and ConstArgKind to selectively "disable" it if we are in an ambig position.

  2. The visit_ty and visit_const_arg methods on HIR visitors only accept the ambig position versions of types/consts.
    Unambig types/consts are implicitly converted to ambig types/consts during the visiting process, with the Infer variant handled by a dedicated visit_infer method.

This has a number of benefits:

  • It's clear that GenericArg::Type/Const cannot represent inferred type/const arguments
  • Implementors of visit_ty and visit_const_arg will never encounter inferred types/consts making it impossible to write a visitor that seems to work right but handles edge cases wrong
  • The visit_infer method handles all cases of inferred type/consts in the HIR making it easy for visitors to handle inferred type/consts in one dedicated place and not forget cases

Src Doc Rustc Dev Guide Src Asm (`src/doc/rustc-dev-guide/src/asm.md`)

Inline assembly

Overview

Inline assembly in rustc mostly revolves around taking an asm! macro invocation and plumbing it
through all of the compiler layers down to LLVM codegen.
Throughout the various stages,
an InlineAsm generally consists of 3 components:

  • The template string, which is stored as an array of InlineAsmTemplatePiece.
    Each piece represents either a literal or a placeholder for an operand
    (just like format strings).

    rust
    pub enum InlineAsmTemplatePiece {
        String(String),
        Placeholder { operand_idx: usize, modifier: Option<char>, span: Span },
    }
  • The list of operands to the asm! (in, [late]out, in[late]out, sym, const).
    These are represented differently at each stage of lowering,
    but follow a common pattern:

    • in, out, and inout all have an associated register class (reg)
      or explicit register ("eax").
    • inout has 2 forms:
      one with a single expression that is both read from and written to,
      and one with two separate expressions for the input and output parts.
    • out and inout have a late flag (lateout / inlateout) to indicate that the register
      allocator is allowed to reuse an input register for this output.
    • out and the split variant of inout allow _ to be specified for an output,
      which means that the output is discarded.
      This is used to allocate scratch registers for assembly code.
    • const refers to an anonymous constants,
      and generally works like an inline const.
    • sym is a bit special since it only accepts a path expression,
      which must point to a static or a fn.
  • The options set at the end of the asm! macro.
    The only ones that are of particular interest to
    rustc are NORETURN which makes asm! return ! instead of (),
    and RAW which disables format string parsing.
    The remaining options are mostly passed through to LLVM with little processing.

    rust
    bitflags::bitflags! {
        pub struct InlineAsmOptions: u16 {
            const PURE = 1 << 0;
            const NOMEM = 1 << 1;
            const READONLY = 1 << 2;
            const PRESERVES_FLAGS = 1 << 3;
            const NORETURN = 1 << 4;
            const NOSTACK = 1 << 5;
            const ATT_SYNTAX = 1 << 6;
            const RAW = 1 << 7;
            const MAY_UNWIND = 1 << 8;
        }
    }

AST

InlineAsm is represented as an expression in the AST with the ast::InlineAsm type.

The asm! macro is implemented in rustc_builtin_macros and outputs an InlineAsm AST node.
The template string is parsed using fmt_macros,
positional and named operands are resolved to explicit operand indices.
Since target information is not available to macro invocations,
validation of the registers and register classes is deferred to AST lowering.

HIR

InlineAsm is represented as an expression in the HIR with the hir::InlineAsm type.

AST lowering is where InlineAsmRegOrRegClass is converted from Symbols to an actual register or
register class.
If any modifiers are specified for a template string placeholder, these are
validated against the set allowed for that operand type.
Finally, explicit registers for inputs and
outputs are checked for conflicts (same register used for different operands).

Type checking

Each register class has an allowlist of types that it may be used with.
After the types of all operands have been determined,
the intrinsicck pass will check that these types are in the allowlist.
It also checks that split inout operands have compatible types and that const
operands are integers or floats.
Suggestions are emitted where needed if a template modifier should
be used for an operand based on the type that was passed into it.

THIR

InlineAsm is represented as an expression in the THIR with the InlineAsmExpr type.

The only significant change compared to HIR is that Sym has been lowered to either a SymFn
whose expr is a Literal ZST of the fn,
or a SymStatic which points to the DefId of a static.

MIR

InlineAsm is represented as a Terminator in the MIR with the TerminatorKind::InlineAsm variant

As part of THIR lowering, InOut and SplitInOut operands are lowered to a split form with a
separate in_value and out_place.

Semantically, the InlineAsm terminator is similar to the Call terminator except that it has
multiple output places where a Call only has a single return place output.

Codegen

Operands are lowered one more time before being passed to LLVM codegen.
This is represented by the InlineAsmOperandRef type from rustc_codegen_ssa.

The operands are lowered to LLVM operands and constraint codes as follows:

  • out and the output part of inout operands are added first, as required by LLVM.
    Late output operands have a = prefix added to their constraint code,
    and non-late output operands have a =& prefix added to their constraint code.
  • in operands are added normally.
  • inout operands are tied to the matching output operand.
  • sym operands are passed as function pointers or pointers,
    using the "s" constraint.
  • const operands are formatted to a string and directly inserted in the template string.

The template string is converted to LLVM form:

  • $ characters are escaped as $$.
  • const operands are converted to strings and inserted directly.
  • Placeholders are formatted as ${X:M},
    where X is the operand index and M is the modifier character.
    Modifiers are converted from the Rust form to the LLVM form.

The various options are converted to clobber constraints or LLVM attributes;
refer to the RFC for more details.

Note that LLVM is sometimes rather picky about what types it accepts for certain constraint codes
so we sometimes need to insert conversions to/from a supported type.
See the target-specific ISelLowering.cpp files in LLVM
for details of what types are supported for each register class.

Adding support for new architectures

Adding inline assembly support to an architecture is mostly a matter of defining the registers and
register classes for that architecture.
All the definitions for register classes are located in
compiler/rustc_target/asm/.

Additionally you will need to implement lowering of these register classes to LLVM constraint codes
in compiler/rustc_codegen_llvm/asm.rs.

When adding a new architecture, make sure to cross-reference with the LLVM source code:

  • LLVM has restrictions on which types can be used with a particular constraint code.
    Refer to the getRegForInlineAsmConstraint function
    in lib/Target/${ARCH}/${ARCH}ISelLowering.cpp.
  • LLVM reserves certain registers for its internal use,
    which causes them to not be saved/restored properly around inline assembly blocks.
    These registers are listed in the getReservedRegs
    function in lib/Target/${ARCH}/${ARCH}RegisterInfo.cpp.
    Any "conditionally" reserved register,
    such as the frame/base pointer,
    must always be treated as reserved for Rust purposes because we
    can't know ahead of time whether a function will require a frame/base pointer.

Tests

Various tests for inline assembly are available:

  • tests/assembly-llvm/asm
  • tests/ui/asm
  • tests/codegen-llvm/asm-*

Every architecture supported by inline assembly must have exhaustive tests in
tests/assembly-llvm/asm which test all combinations of register classes and types.


Src Doc Rustc Dev Guide Src Ast Validation (`src/doc/rustc-dev-guide/src/ast-validation.md`)

AST validation

AST validation is a separate AST pass that visits each
item in the tree and performs simple checks.
This pass doesn't perform any complex analysis, type checking or
name resolution.

Before performing any validation, the compiler first expands the macros.
Then this pass performs validations to check that each AST item is in the correct state.
And when this pass is done, the compiler runs the crate resolution pass.

Validations

Validations are defined in AstValidator type, which
itself is located in rustc_ast_passes crate.
This type implements various simple checks which emit errors
when certain language rules are broken.

In addition, AstValidator implements Visitor trait
that defines how to visit AST items (which can be functions,
traits, enums, etc).

For each item, visitor performs specific checks.
For example, when visiting a function declaration,
AstValidator checks that the function has:

  • no more than u16::MAX parameters;
  • c-variadic argument goes the last in the declaration;
  • documentation comments aren't applied to function parameters;
  • and other validations.

Src Doc Rustc Dev Guide Src Attributes (`src/doc/rustc-dev-guide/src/attributes.md`)

Attributes

Attributes come in two types: inert (or built-in) and active (non-builtin).

Builtin/inert attributes

These attributes are defined in the compiler itself, in
compiler/rustc_feature/src/builtin_attrs.rs and in the attribute parsers.

Examples include #[allow] and #[macro_use].

These attributes have several important characteristics:

  • They are always in scope, and do not participate in typical path-based resolution.
  • They cannot be renamed.
    For example, use allow as foo will compile, but writing #[foo] will produce an error.
  • They are 'inert', meaning they are left as-is by the macro expansion code.
    As a result, any behavior comes as a result of the compiler explicitly checking for their presence.
    For example, lint-related code explicitly checks for #[allow], #[warn], #[deny], and
    #[forbid], rather than the behavior coming from the expansion of the attributes themselves.

For more information on these attributes, see the chapter about attribute parsing.

Note on adding new builtin attributes

Warning: Name resolution ambiguity potential when adding new builtin attributes

Please note that adding new builtin attributes (whose name is not reserved, i.e. a new builtin
attribute whose name does not start with rustc), even if unstable-gated, can introduce breakage
from name resolution ambiguity in stable code if (1) the stable code has a macro of the same name
which gets re-exported, or (2) or a proc-macro derive helper attribute of the same name.

Typically, the builtin attributes probably has to start out as #[rustc_foo] instead of #[foo] to
avoid colliding with user-defined macros and proc-macro helper attributes.
Then, prior to stabilization,
a rename to #[foo] should be done separately with a crater run to assess fallout,
with a deliberate breakage FCP proposal for T-lang to consider.

Remember also that crater is not exhaustive and does not contain all existing stable code.

See:

'Non-builtin'/'active' attributes

These attributes are defined by a crate - either the standard library, or a proc-macro crate.

Important: Many non-builtin attributes, such as #[derive], are still considered part of the
core Rust language.
However, they are not called 'builtin attributes', since they have a
corresponding definition in the standard library.

Definitions of non-builtin attributes take two forms:

  1. Proc-macro attributes, defined via a function annotated with #[proc_macro_attribute] in a
    proc-macro crate.
  2. AST-based attributes, defined in the standard library.
    These attributes have special 'stub'
    macros defined in places like library/core/src/macros/mod.rs.

These definitions exist to allow the macros to participate in typical path-based resolution - they
can be imported, re-exported, and renamed just like any other item definition.
However, the body of the definition is empty.
Instead, the macro is annotated with the #[rustc_builtin_macro]
attribute, which tells the compiler to run a corresponding function in rustc_builtin_macros.

All non-builtin attributes have the following characteristics:

  • Like all other definitions (e.g. structs), they must be brought into scope via an import.
    Many standard library attributes are included in the prelude - this is why writing #[derive]
    works without an import.
  • They participate in macro expansion.
    The implementation of the macro may leave the attribute
    target unchanged, modify the target, produce new AST nodes, or remove the target entirely.

Src Doc Rustc Dev Guide Src Borrow Check (`src/doc/rustc-dev-guide/src/borrow-check.md`)

MIR borrow check

The borrow check is Rust's "secret sauce" – it is tasked with enforcing a number of properties:

  • That all variables are initialized before they are used.
  • That you can't move the same value twice.
  • That you can't move a value while it is borrowed.
  • That you can't access a place while it is mutably borrowed (except through the reference).
  • That you can't mutate a place while it is immutably borrowed.
  • etc

The borrow checker operates on the MIR.
An older implementation operated on the HIR.
Doing borrow checking on MIR has several advantages:

Major phases of the borrow checker

The borrow checker source is found in the rustc_borrowck crate.
The main entry point is the mir_borrowck query.

  • We first create a local copy of the MIR.
    In the coming steps,
    we will modify this copy in place to modify the types and things to
    include references to the new regions that we are computing.
  • We then invoke replace_regions_in_mir to modify our local MIR.
    Among other things, this function will replace all of the regions
    in the MIR with fresh inference variables.
  • Next, we perform a number of dataflow analyses that
    compute what data is moved and when.
  • We then do a second type check across the MIR:
    the purpose of this type check is to determine all of the constraints between different regions.
  • Next, we do region inference, which computes
    the values of each region — basically, the points in the control-flow graph where
    each lifetime must be valid according to the constraints we collected.
  • At this point, we can compute the "borrows in scope" at each point.
  • Finally, we do a second walk over the MIR, looking at the actions it does and reporting errors.
    For example, if we see a statement like *a + 1,
    we would check that the variable a is initialized and that it is not mutably borrowed,
    as either of those would require an error to be reported.
    Doing this check requires the results of all the previous analyses.

Debugging the borrow checker

See Debugging the borrow checker and MIR Debugging.


Src Doc Rustc Dev Guide Src Bug Fix Procedure (`src/doc/rustc-dev-guide/src/bug-fix-procedure.md`)

Procedures for breaking changes

This page defines the best practices procedure for making bug fixes or soundness
corrections in the compiler that can cause existing code to stop compiling.
This text is based on RFC 1589.

Motivation

From time to time, we encounter the need to make a bug fix, soundness
correction, or other change in the compiler which will cause existing code to stop compiling.
When this happens, it is important that we handle the change in
a way that gives users of Rust a smooth transition.
What we want to avoid is that existing programs suddenly stop compiling with opaque error messages:
we would prefer to have a gradual period of warnings, with clear guidance as to
what the problem is, how to fix it, and why the change was made.
This RFC describes the procedure that we have been developing for handling breaking
changes that aims to achieve that kind of smooth transition.

One of the key points of this policy is that (a) warnings should be issued
initially rather than hard errors if at all possible and (b) every change that
causes existing code to stop compiling will have an associated tracking issue.
This issue provides a point to collect feedback on the results of that change.
Sometimes changes have unexpectedly large consequences or there may be a way to
avoid the change that was not considered.
In those cases,
we may decide to change course and roll back the change,
or find another solution (and if warnings are being used, this is particularly easy to do).

What qualifies as a bug fix?

Note that this RFC does not try to define when a breaking change is permitted.
That is already covered under RFC 1122.
This document assumes that the change being made is in accordance with those policies.
Here is a summary of the conditions from RFC 1122:

  • Soundness changes: Fixes to holes uncovered in the type system.
  • Compiler bugs: Places where the compiler is not implementing the specified
    semantics found in an RFC or lang-team decision.
  • Underspecified language semantics: Clarifications to grey areas where the
    compiler behaves inconsistently and no formal behavior had been previously decided.

Please see the RFC for full details!

Detailed design

The procedure for making a breaking change is as follows (each of these steps is
described in more detail below):

  1. Do a crater run to assess the impact of the change.
  2. Make a special tracking issue dedicated to the change.
  3. Do not report an error right away.
    Instead, issue forwards-compatibility lint warnings.
    • Sometimes this is not straightforward.
      See the text below for suggestions on different techniques we have employed in the past.
    • For cases where warnings are infeasible:
      • Report errors, but make every effort to give a targeted error message
        that directs users to the tracking issue
      • Submit PRs to all known affected crates that fix the issue
        • or, at minimum, alert the owners of those crates to the problem and
          direct them to the tracking issue
  4. Once the change has been in the wild for at least one cycle, we can
    stabilize the change, converting those warnings into errors.

Finally, for changes to rustc_ast that will affect plugins, the general policy
is to batch these changes.
That is discussed below in more detail.

Tracking issue

Every breaking change should be accompanied by a dedicated tracking issue for that change.
The main text of this issue should describe the change being
made, with a focus on what users must do to fix their code.
The issue should be approachable and practical; it may make sense to direct users to an RFC or some
other issue for the full details.
The issue also serves as a place where users can comment with questions or other concerns.

A template for these breaking-change tracking issues can be found here.
An example of how such an issue should look can be found here.

Issuing future compatibility warnings

The best way to handle a breaking change is to begin by issuing future-compatibility warnings.
These are a special category of lint warning.
Adding a new future-compatibility warning can be done as follows.

rust
// 1. Define the lint in `compiler/rustc_lint/src/builtin.rs` and
//    add the metadata for the future incompatibility:
declare_lint! {
    pub YOUR_LINT_HERE,
    Warn,
    "illegal use of foo bar baz"
    @future_incompatible = FutureIncompatibleInfo {
        reason: fcw!(FutureReleaseError #1234) // your tracking issue here!
    },
}

// 2. Add a dedicated lint pass for it.
//    This step can be skipped if you emit the lint as part of an existing pass.

#[derive(Default)]
pub struct MyLintPass {
    ...
}

impl {Early,Late}LintPass for MyLintPass {
    ...
}

impl_lint_pass!(MyLintPass => [YOUR_LINT_HERE]);

// 3. emit the lint somewhere in your lint pass:
cx.emit_span_lint(
    YOUR_LINT_HERE,
    pat.span,
    // some diagnostic struct
    MyDiagnostic {
        ...
    },
);

Finally, register the lint in compiler/rustc_lint/src/lib.rs.
There are many examples in that file that already show how to do so.

Helpful techniques

It can often be challenging to filter out new warnings from older, pre-existing errors.
One technique that has been used in the past is to run the older code
unchanged and collect the errors it would have reported.
You can then issue warnings for any errors you would give which do not appear in that original set.
Another option is to abort compilation after the original code completes if
errors are reported: then you know that your new code will only execute when
there were no errors before.

Crater and crates.io

Crater is a bot that will compile all crates.io crates and many
public github repos with the compiler with your changes.
A report will then be generated with crates that ceased to compile with,
or began to compile with your changes.
Crater runs can take a few days to complete.

We should always do a crater run to assess impact.
It is polite and considerate to notify the authors of crates affected by the breaking change.
It is even better to submit PRs fixing the breakage.

Is it ever acceptable to go directly to issuing errors?

Changes that are believed to have negligible impact can go directly to issuing an error.
One rule of thumb would be to check against crates.io: if fewer than
10 total affected projects are found (not root errors), we can move straight to an error.
In such cases, we should still make the "breaking change"
page as before, and we should ensure that the error directs users to this page.
In other words, everything should be the same except that users are getting an
error, and not a warning.
Moreover, we should submit PRs to the affected projects
(ideally before the PR implementing the change lands in rustc).

If the impact is not believed to be negligible (e.g., more than 10 crates are
affected), then warnings are required (unless the compiler team agrees to grant
a special exemption in some particular case).
If implementing warnings is not feasible,
then we should make an aggressive strategy of migrating crates before
we land the change so as to lower the number of affected crates.
Here are some techniques for approaching this scenario:

  1. Issue warnings for subparts of the problem, and reserve the new errors for
    the smallest set of cases you can.
  2. Try to give a very precise error message that suggests how to fix the problem
    and directs users to the tracking issue.
  3. It may also make sense to layer the fix:
    • First, add warnings where possible and let those land before proceeding to issue errors.
    • Work with authors of affected crates to ensure that corrected versions are
      available before the fix lands, so that downstream users can use them.

Stabilization

After a change is made, we will stabilize the change using the same process
that we use for unstable features:

  • After a new release is made, we will go through the outstanding tracking
    issues corresponding to breaking changes and nominate some of them for final
    comment period
    (FCP).

  • The FCP for such issues lasts for one cycle.
    In the final week or two of the cycle, we will review comments and make a final determination:

    • Convert to error: the change should be made into a hard error.
    • Revert: we should remove the warning and continue to allow the older code to compile.
    • Defer: can't decide yet, wait longer, or try other strategies.

Ideally, breaking changes should have landed on the stable branch of the
compiler before they are finalized.

Removing a lint

Once we have decided to make a "future warning" into a hard error, we need a PR
that removes the custom lint.
As an example, here are the steps required to
remove the overlapping_inherent_impls compatibility lint.
First, convert the name of the lint to uppercase (OVERLAPPING_INHERENT_IMPLS);
search the source for that string.
We will basically by converting each place where this
lint name is mentioned (in the compiler, we use the upper-case name, and a macro
automatically generates the lower-case string; so searching for
overlapping_inherent_impls would not find much).

NOTE: these exact files don't exist anymore, but the procedure is still the same.

Remove the lint.

The first reference you will likely find is the lint definition in
rustc_session/src/lint/builtin.rs that resembles this
:

rust
declare_lint! {
    pub OVERLAPPING_INHERENT_IMPLS,
    Deny, // this may also say Warning
    "two overlapping inherent impls define an item with the same name were erroneously allowed",
    @future_incompatible = FutureIncompatibleInfo {
        reason: fcw!(FutureReleaseError #1234), // your tracking issue here!
    },
}

This declare_lint! macro creates the relevant data structures.
Remove it.
You will also find that there is a mention of OVERLAPPING_INHERENT_IMPLS later in
the file as part of a lint_array!; remove it too.

Add the lint to the list of removed lints.

In compiler/rustc_lint/src/lib.rs there is a list of "renamed and removed lints".
You can add this lint to the list:

rust
store.register_removed("overlapping_inherent_impls", "converted into hard error, see #36889");

where #36889 is the tracking issue for your lint.

Update the places that issue the lint

Finally, the last class of references you will see are the places that actually
trigger the lint itself (i.e., what causes the warnings to appear). These
you do not want to delete.
Instead, you want to convert them into errors.
In this case, the add_lint call looks like this:

rust
self.tcx.sess.add_lint(lint::builtin::OVERLAPPING_INHERENT_IMPLS,
                       node_id,
                       self.tcx.span_of_impl(item1).unwrap(),
                       msg);

You'll also often find node_span_lint used for this.

We want to convert this into an error.
In some cases, there may be an existing error for this scenario.
In others, we will need to allocate a fresh diagnostic code.
Instructions for allocating a fresh diagnostic
code can be found here.
You may want
to mention in the extended description that the compiler behavior
changed on this point, and include a reference to the tracking issue for the change.

Let's say that we've adopted E0592 as our code.
Then we can change the add_lint() call above to something like:

rust
struct_span_code_err!(self.dcx(), self.tcx.span_of_impl(item1).unwrap(), E0592, msg)
    .emit();

Or better: a structured diagnostic like this:

rust
#[derive(Diagnostic)]
struct MyDiagnostic {
    #[label]
    span: Span,
    ...
}

Update tests

Finally, run the test suite.
These should be some tests that used to reference
the overlapping_inherent_impls lint; those will need to be updated.
In general, if the test used to have #[deny(overlapping_inherent_impls)], that
can just be removed.

text
./x test

All done!

Open a PR.


Src Doc Rustc Dev Guide Src Cli (`src/doc/rustc-dev-guide/src/cli.md`)

Command-line Arguments

Command-line flags are documented in the rustc book.
All stable flags should be documented there.
Unstable flags should be documented in the unstable book.

See the forge guide for new options for details on the procedure for
adding a new command-line argument.

Guidelines

  • Flags should be orthogonal to each other.
    For example, if we'd have a json-emitting variant of multiple actions foo and bar,
    an additional --json flag is better than adding --foo-json and --bar-json.
  • Avoid flags with the no- prefix.
    Instead, use the parse_bool function,
    such as -C embed-bitcode=no.
  • Consider the behavior if the flag is passed multiple times.
    In some situations, the values should be accumulated (in order!).
    In other situations, subsequent flags should override previous flags (for example,
    the lint-level flags).
    And some flags (like -o) should generate an error
    if it is too ambiguous what multiple flags would mean.
  • Always give options a long descriptive name, if only for more understandable compiler scripts.
  • The --verbose flag is for adding verbose information to rustc output.
    For example,
    using it with the --version flag gives information about the hashes of the compiler code.
  • Experimental flags and options must be guarded behind the -Z unstable-options flag.

Src Doc Rustc Dev Guide Src Closure (`src/doc/rustc-dev-guide/src/closure.md`)

Closure Capture Inference

This section describes how rustc handles closures.
Closures in Rust are effectively "desugared" into structs that contain the values they use (or
references to the values they use) from their creator's stack frame.
rustc has the job of figuring out which values a closure uses and how, so it can decide
whether to capture a given variable by shared reference, mutable reference, or by move.
rustc also has to figure out which of the closure traits (Fn,
FnMut, or FnOnce) a closure is capable of implementing.

Let's start with a few examples:

Example 1

To start, let's take a look at how the closure in the following example is desugared:

rust
fn closure(f: impl Fn()) {
    f();
}

fn main() {
    let x: i32 = 10;
    closure(|| println!("Hi {}", x));  // The closure just reads x.
    println!("Value of x after return {}", x);
}

Let's say the above is the content of a file called immut.rs.
If we compile immut.rs using the following command,
the -Z dump-mir=all flag will cause
rustc to generate and dump the MIR to a directory called mir_dump.

console
> rustc +stage1 immut.rs -Z dump-mir=all

After we run this command, we will see a newly generated directory in our
current working directory called mir_dump, which will contain several files.
If we look at file rustc.main.-------.mir_map.0.mir, we will find, among other things,
it also contains this line:

rust,ignore
_4 = &_1;
_3 = [[email protected]:7:13: 7:36] { x: move _4 };

Note that in the MIR examples in this chapter, _1 is x.

Here in first line _4 = &_1;,
the mir_dump tells us that x was borrowed as an immutable reference.
This is what we would hope as our closure just reads x.

Example 2

Here is another example:

rust
fn closure(mut f: impl FnMut()) {
    f();
}

fn main() {
    let mut x: i32 = 10;
    closure(|| {
        x += 10;  // The closure mutates the value of x
        println!("Hi {}", x)
    });
    println!("Value of x after return {}", x);
}
rust,ignore
_4 = &mut _1;
_3 = [[email protected]:7:13: 10:6] { x: move _4 };

This time along, in the line _4 = &mut _1;, we see that the borrow is changed to mutable borrow.
Fair enough!
The closure increments x by 10.

Example 3

One more example:

rust
fn closure(f: impl FnOnce()) {
    f();
}

fn main() {
    let x = vec![21];
    closure(|| {
        drop(x);  // Makes x unusable after the fact.
    });
    // println!("Value of x after return {:?}", x);
}
rust,ignore
_6 = [[email protected]:7:13: 9:6] { x: move _1 }; // bb16[3]: scope 1 at move.rs:7:13: 9:6

Here, x is directly moved into the closure,
and the access to it will not be permitted after the closure.

Inferences in the compiler

Now let's dive into rustc code and see how all these inferences are done by the compiler.

Let's start with defining a term that we will be using quite a bit in the rest of the discussion -
upvar.
An upvar is a variable that is local to the function where the closure is defined.
So, in the above examples, x will be an upvar to the closure.
They are also sometimes referred to as the free variables,
meaning they are not bound to the context of the closure.
compiler/rustc_passes/src/upvars.rs defines a query called upvars_mentioned
for this purpose.

Other than lazy invocation,
one other thing that distinguishes a closure from a normal function is that it can use the upvars.
It borrows these upvars from its surrounding
context; therefore the compiler has to determine the upvar's borrow type.
The compiler starts with assigning an immutable borrow type and lowers the restriction
(that is, changes it from immutable to mutable to move) as needed, based on the usage.
In the Example 1 above,
the closure only uses the variable for printing but does not modify it in any way and therefore,
in the mir_dump, we find the borrow type for the upvar x to be immutable.
In example 2, however, the closure modifies x and increments it by some value.
Because of this mutation, the compiler, which
started off assigning x as an immutable reference type, has to adjust it as a mutable reference.
Likewise in the third example, the closure drops the vector and therefore this requires the variable
x to be moved into the closure.
Depending on the borrow kind, the closure has to implement the
appropriate trait: Fn trait for immutable borrow, FnMut for mutable borrow,
and FnOnce for move semantics.

Most of the code related to the closure is in the
compiler/rustc_hir_typeck/src/upvar.rs file and the data structures are
declared in the file compiler/rustc_middle/src/ty/mod.rs.

Before we go any further,
let's discuss how we can examine the flow of control through the rustc codebase.
For closures specifically,
set the RUSTC_LOG env variable as below and collect the output in a file:

console
> RUSTC_LOG=rustc_hir_typeck::upvar rustc +stage1 -Z dump-mir=all \
    <.rs file to compile> 2> <file where the output will be dumped>

This uses the stage1 compiler and enables debug! logging for the rustc_hir_typeck::upvar module.

The other option is to step through the code using lldb or gdb.

  1. rust-lldb build/host/stage1/bin/rustc test.rs
  2. In lldb:
    1. b upvar.rs:134 // Setting the breakpoint on a certain line in the upvar.rs file
    2. r // Run the program until it hits the breakpoint

Let's start with upvar.rs.
This file has something called the euv::ExprUseVisitor which walks the source of the closure and
invokes a callback for each upvar that is borrowed, mutated, or moved.

rust
fn main() {
    let mut x = vec![21];
    let _cl = || {
        let y = x[0];  // 1.
        x[0] += 1;  // 2.
    };
}

In the above example, our visitor will be called twice, for the lines marked 1 and 2,
once for a shared borrow and another one for a mutable borrow.
It will also tell us what was borrowed.

The callbacks are defined by implementing the Delegate trait.
The InferBorrowKind type implements Delegate and keeps a map that
records for each upvar which mode of capture was required.
The modes of capture can be ByValue (moved) or ByRef (borrowed).
For ByRef borrows, the possible BorrowKinds are ImmBorrow,
UniqueImmBorrow, MutBorrow as defined in the
compiler/rustc_middle/src/ty/mod.rs.

Delegate defines a few different methods (the different callbacks):
consume for move of a variable, borrow for a borrow of some kind (shared or mutable),
and mutate when we see an assignment of something.

All of these callbacks have a common argument cmt which stands for Category,
Mutability and Type and is defined in compiler/rustc_hir_typeck/src/expr_use_visitor.rs.
Borrowing from the code comments, "cmt is a complete categorization of a value indicating where it
originated and how it is located, as well as the mutability of the memory in
which the value is stored". Based on the callback (consume, borrow etc.), we
will call the relevant adjust_upvar_borrow_kind_for_<something> and pass the cmt along.
Once the borrow type is adjusted, we store it in the table,
which basically says what borrows were made for each closure.

rust,ignore
self.tables
    .borrow_mut()
    .upvar_capture_map
    .extend(delegate.adjust_upvar_captures);

Src Doc Rustc Dev Guide Src Coherence (`src/doc/rustc-dev-guide/src/coherence.md`)

Coherence

NOTE: this is based on notes by @lcnr

Coherence checking is what detects both of trait impls and inherent impls overlapping with others.
(reminder: inherent impls are impls of concrete types like impl MyStruct {})

Overlapping trait impls always produce an error,
while overlapping inherent impls result in an error only if they have methods with the same name.

Checking for overlaps is split in two parts.
First there's the overlap check(s),
which finds overlaps between traits and inherent implementations that the compiler currently knows about.

However, Coherence also results in an error if any other impls could exist,
even if they are currently unknown.
This affects impls which may get added to upstream crates in a backwards compatible way,
and impls from downstream crates.
This is called the Orphan check.

Overlap checks

Overlap checks are performed for both inherent impls, and for trait impls.
This uses the same overlap checking code, really done as two separate analyses.
Overlap checks always consider pairs of implementations, comparing them to each other.

Overlap checking for inherent impl blocks is done through fn check_item (in coherence/inherent_impls_overlap.rs),
where you can very clearly see that (at least for small n), the check really performs n^2
comparisons between impls.

In the case of traits, this check is currently done as part of building the specialization graph,
to handle specializing impls overlapping with their parent, but this may change in the future.

In both cases, all pairs of impls are checked for overlap.

Overlapping is sometimes partially allowed:

  1. for marker traits
  2. under specialization

It normally isn't.

The overlap check has various modes (see OverlapMode).
Importantly, there's the explicit negative impl check, and the implicit negative impl check.
Both try to prove that an overlap is definitely impossible.

The explicit negative impl check

This check is done in impl_intersection_has_negative_obligation.

This check tries to find a negative trait implementation.
For example:

rust
struct MyCustomErrorType;

// both in your own crate
impl From<&str> for MyCustomErrorType {}
impl<E> From<E> for MyCustomErrorType where E: Error {}

In this example, we'd get:
MyCustomErrorType: From<&str> and MyCustomErrorType: From<?E>, giving ?E = &str.

And thus, these two implementations would overlap.
However, libstd provides &str: !Error, and therefore guarantees that there
will never be a positive implementation of &str: Error, and thus there is no overlap.

Note that for this kind of negative impl check, we must have explicit negative implementations provided.
This is not currently stable.

The implicit negative impl check

This check is done in impl_intersection_has_impossible_obligation,
and does not rely on negative trait implementations and is stable.

Let's say there's a

rust
impl From<MyLocalType> for Box<dyn Error> {}  // in your own crate
impl<E> From<E> for Box<dyn Error> where E: Error {} // in std

This would give: Box<dyn Error>: From<MyLocalType>, and Box<dyn Error>: From<?E>,
giving ?E = MyLocalType.

In your crate there's no MyLocalType: Error, downstream crates cannot implement Error (a remote trait) for MyLocalType (a remote type).
Therefore, these two impls do not overlap.
Importantly, this works even if there isn't a impl !Error for MyLocalType.


Src Doc Rustc Dev Guide Src Compiler Debugging (`src/doc/rustc-dev-guide/src/compiler-debugging.md`)

Debugging the compiler

This chapter contains a few tips to debug the compiler.
These tips aim to be useful no matter what you are working on.
Some of the other chapters have
advice about specific parts of the compiler (e.g. the Queries Debugging and
Testing chapter
or the LLVM Debugging chapter).

Configuring the compiler

By default, rustc is built without most debug information.
To enable debug info,
set rust.debug = true in your bootstrap.toml.

Setting rust.debug = true turns on many different debug options (e.g., debug-assertions,
debug-logging, etc.) which can be individually tweaked if you want to, but many people
simply set rust.debug = true.

If you want to use GDB to debug rustc, please set bootstrap.toml with options:

toml
rust.debug = true
rust.debuginfo-level = 2

NOTE:
This will use a lot of disk space
(upwards of 35GB),
and will take a lot more compile time.
With debuginfo-level = 1 (the default when debug = true),
you will be able to track the execution path,
but will lose the symbol information for debugging.

The default configuration will enable symbol-mangling-version v0.
This requires at least GDB v10.2,
otherwise you need to disable new symbol-mangling-version in bootstrap.toml.

toml
rust.new-symbol-mangling = false

See the comments in bootstrap.example.toml for more info.

You will need to rebuild the compiler after changing any configuration option.

Suppressing the ICE file

By default, if rustc encounters an Internal Compiler Error (ICE) it will dump the ICE contents to an
ICE file within the current working directory named rustc-ice-<timestamp>-.txt.
If this is not desirable, you can prevent the ICE file from being created with RUSTC_ICE=0.

Getting a backtrace

When you have an ICE (panic in the compiler),
you can set RUST_BACKTRACE=1 to get the stack trace of the panic! like in normal Rust programs.
IIRC backtraces don't work on MinGW, sorry.
If you have trouble or the backtraces are full of unknown,
you might want to find some way to use Linux, Mac, or MSVC on Windows.

In the default configuration (without debug set to true), you don't have line numbers enabled,
so the backtrace looks like this:

text
stack backtrace:
   0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
   1: std::sys_common::backtrace::_print
   2: std::panicking::default_hook::{{closure}}
   3: std::panicking::default_hook
   4: std::panicking::rust_panic_with_hook
   5: std::panicking::begin_panic
   (~~~~ LINES REMOVED BY ME FOR BREVITY ~~~~)
  32: rustc_typeck::check_crate
  33: <std::thread::local::LocalKey<T>>::with
  34: <std::thread::local::LocalKey<T>>::with
  35: rustc::ty::context::TyCtxt::create_and_enter
  36: rustc_driver::driver::compile_input
  37: rustc_driver::compiler_entrypoint

If you set debug = true, you will get line numbers for the stack trace.
Then the backtrace will look like this:

text
stack backtrace:
   (~~~~ LINES REMOVED BY ME FOR BREVITY ~~~~)
             at /home/user/rust/compiler/rustc_typeck/src/check/cast.rs:110
   7: rustc_typeck::check::cast::CastCheck::check
             at /home/user/rust/compiler/rustc_typeck/src/check/cast.rs:572
             at /home/user/rust/compiler/rustc_typeck/src/check/cast.rs:460
             at /home/user/rust/compiler/rustc_typeck/src/check/cast.rs:370
   (~~~~ LINES REMOVED BY ME FOR BREVITY ~~~~)
  33: rustc_driver::driver::compile_input
             at /home/user/rust/compiler/rustc_driver/src/driver.rs:1010
             at /home/user/rust/compiler/rustc_driver/src/driver.rs:212
  34: rustc_driver::compiler_entrypoint
             at /home/user/rust/compiler/rustc_driver/src/lib.rs:253

`-Z` flags

The compiler has a bunch of -Z * flags.
These are unstable flags that are only enabled on nightly.
Many of them are useful for debugging.
To get a full listing of -Z flags, use -Z help.

One useful flag is -Z verbose-internals,
which generally enables printing more info that could be useful for debugging.

Right below you can find elaborate explainers on a selected few.

Getting a backtrace for errors

If you want to get a backtrace to the point where the compiler emits an error message,
you can pass the -Z treat-err-as-bug=n, which will make the compiler panic on the nth error.
If you leave off =n,
the compiler will assume 1 for n and thus panic on the first error it encounters.

For example:

bash
$ cat error.rs
rust
fn main() {
    1 + ();
}
text
$ rustc +stage1 error.rs
error[E0277]: cannot add `()` to `{integer}`
 --> error.rs:2:7
  |
2 |       1 + ();
  |         ^ no implementation for `{integer} + ()`
  |
  = help: the trait `Add<()>` is not implemented for `{integer}`

error: aborting due to previous error

Now, where does the error above come from?

text
/* Detailed source-code truncated for AI context efficiency. */

Cool, now I have a backtrace for the error!

Debugging delayed bugs

The -Z eagerly-emit-delayed-bugs option makes it easy to debug delayed bugs.
It turns them into normal errors, i.e. makes them visible. This can be used in
combination with -Z treat-err-as-bug to stop at a particular delayed bug and get a backtrace.

Getting the error creation location

-Z track-diagnostics can help figure out where errors are emitted.
It uses #[track_caller] for this and prints its location alongside the error:

text
$ RUST_BACKTRACE=1 rustc +stage1 error.rs -Z track-diagnostics
error[E0277]: cannot add `()` to `{integer}`
 --> src\error.rs:2:7
  |
2 |     1 + ();
  |       ^ no implementation for `{integer} + ()`
-Ztrack-diagnostics: created at compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs:638:39
  |
  = help: the trait `Add<()>` is not implemented for `{integer}`
  = help: the following other types implement trait `Add<Rhs>`:
            <&'a f32 as Add<f32>>
            <&'a f64 as Add<f64>>
            <&'a i128 as Add<i128>>
            <&'a i16 as Add<i16>>
            <&'a i32 as Add<i32>>
            <&'a i64 as Add<i64>>
            <&'a i8 as Add<i8>>
            <&'a isize as Add<isize>>
          and 48 others

For more information about this error, try `rustc --explain E0277`.

This is similar but different to -Z treat-err-as-bug:

  • it will print the locations for all errors emitted
  • it does not require a compiler built with debug symbols
  • you don't have to read through a big stack trace.

Getting logging output

The compiler uses the tracing crate for logging.

For details, see the chapter on tracing.

Narrowing (bisecting) regressions

The cargo-bisect-rustc tool can be used as a quick and easy way to
find exactly which PR caused a change in rustc behavior.
It automatically downloads rustc PR artifacts and tests them against a project you provide
until it finds the regression.
You can then look at the PR to get more context on why it was changed.
See this tutorial on how to use it.

Downloading artifacts from Rust's CI

The rustup-toolchain-install-master tool by kennytm can be used to
download the artifacts produced by Rust's CI for a specific SHA1 -- this
basically corresponds to the successful landing of some PR -- and then sets
them up for your local use.
This also works for artifacts produced by @bors try.
This is helpful when you want to examine the resulting build of a PR
without doing the build yourself.

`#[rustc_*]` TEST attributes

The compiler defines a whole lot of internal (perma-unstable) attributes some of which are useful
for debugging by dumping extra compiler-internal information.
These are prefixed with rustc_ and
are gated behind the internal feature rustc_attrs (enabled via e.g. #![feature(rustc_attrs)]).

For a complete and up to date list, see builtin_attrs.
More specifically, the ones marked TEST.
Here are some notable ones:

Attribute Description
rustc_dump_def_parents Dumps the chain of DefId parents of certain definitions.
rustc_dump_def_path Dumps the def_path_str of an item.
rustc_dump_generics Dumps the generics of an item.
rustc_dump_hidden_type_of_opaques Dumps the hidden type of each opaque types in the crate.
rustc_dump_inferred_outlives Dumps implied bounds of an item. More precisely, the inferred_outlives_of an item.
rustc_dump_item_bounds Dumps the item_bounds of an item.
rustc_dump_layout See this section.
rustc_dump_object_lifetime_defaults Dumps the object lifetime defaults of an item.
rustc_dump_clauses Dumps the clauses_of an item.
rustc_dump_symbol_name Dumps the mangled & demangled symbol_name of an item.
rustc_dump_variances Dumps the variances of an item.
rustc_dump_vtable Dumps the vtable layout of an impl, or a type alias of a dyn type.
rustc_regions Dumps NLL closure region requirements.

Right below you can find elaborate explainers on a selected few.

Formatting Graphviz output (.dot files)

Some compiler options for debugging specific features yield graphviz graphs -
e.g. the #[rustc_mir(borrowck_graphviz_postflow="suffix.dot")] attribute
on a function dumps various borrow-checker dataflow graphs in conjunction with
-Zdump-mir-dataflow.

These all produce .dot files. To view these files, install graphviz (e.g.
apt-get install graphviz) and then run the following commands:

bash
$ dot -T pdf maybe_init_suffix.dot > maybe_init_suffix.pdf
$ firefox maybe_init_suffix.pdf # Or your favorite pdf viewer

Graphviz also comes with a preprocessor program,
unflatten,
that sometimes helps making the outputs look less oddly spread out.
It reads a dot file and outputs another dot file, so you can use it in a pipe,
e.g:

text
$ unflatten mir_dump/*.foo.-------.nll.0.regioncx.all.dot | dot -Tpdf  -o foo-outlives.pdf

This is particularly useful for complicated region outlives graphs from
the borrow checker.

An online Graphviz editor and visualiser is
also available
.

Narrowing (Bisecting) Regressions

The internal attribute #[rustc_dump_layout(...)] can be used to dump the
Layout of the type it is attached to.
For example:

rust
#![feature(rustc_attrs)]

#[rustc_dump_layout(debug)]
type T<'a> = &'a u32;

Will emit the following:

text
error: layout_of(&u32) = Layout {
           size: Size(8 bytes),
           align: AbiAlign {
               abi: Align(8 bytes),
           },
           backend_repr: Scalar(
               Initialized {
                   value: Pointer(
                       AddressSpace(
                           0,
                       ),
                   ),
                   valid_range: 1..=18446744073709551615,
               },
           ),
           fields: Primitive,
           largest_niche: Some(
               Niche {
                   offset: Size(0 bytes),
                   value: Pointer(
                       AddressSpace(
                           0,
                       ),
                   ),
                   valid_range: 1..=18446744073709551615,
               },
           ),
           uninhabited: false,
           variants: Single {
               index: 0,
           },
           max_repr_align: None,
           unadjusted_abi_align: Align(8 bytes),
           randomization_seed: 281492156579847,
       }
 --> src/lib.rs:4:1
  |
4 | type T<'a> = &'a u32;
  | ^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

Debugging borrowcheck

Debugging the borrow checker has its own chapter.

Configuring CodeLLDB for debugging `rustc`

If you are using VSCode, and have edited your bootstrap.toml to request debugging
level 1 or 2 for the parts of the code you're interested in,
then you should be able to use the CodeLLDB extension in VSCode to debug it.

Here is a sample launch.json file, being used to run a stage 1 compiler direct
from the directory where it is built (does not have to be "installed"):

javascript
// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
      {
        "type": "lldb",
        "request": "launch",
        "name": "Launch",
        "args": [],  // array of string command-line arguments to pass to compiler
        "program": "${workspaceFolder}/build/host/stage1/bin/rustc",
        "windows": {  // applicable if using windows
            "program": "${workspaceFolder}/build/host/stage1/bin/rustc.exe"
        },
        "cwd": "${workspaceFolder}",  // current working directory at program start
        "stopOnEntry": false,
        "sourceLanguages": ["rust"]
      }
    ]
  }

Src Doc Rustc Dev Guide Src Compiler Src (`src/doc/rustc-dev-guide/src/compiler-src.md`)

High-level overview of the compiler source

Now that we have seen what the compiler does,
let's take a look at the structure of the rust-lang/rust repository,
where the rustc source code lives.

You may find it helpful to read the "Overview of the compiler"
chapter, which introduces how the compiler works, before this one.

Workspace structure

The rust-lang/rust repository consists of a single large cargo workspace
containing the compiler, the standard libraries (core, alloc, std,
proc_macro, etc), and rustdoc, along with the build system and a
bunch of tools and submodules for building a full Rust distribution.

The repository consists of three main directories:

Compiler

The compiler is implemented in the various compiler/ crates.
The compiler/ crates all have names starting with rustc_*. These are a
collection of around 50 interdependent crates ranging in size from tiny to
huge. There is also the rustc crate which is the actual binary (i.e. the
main function); it doesn't actually do anything besides calling the
rustc_driver crate, which drives the various parts of compilation in other
crates.

The dependency order of these crates is complex, but roughly it is
something like this:

  1. rustc (the binary) calls rustc_driver::main.
  2. rustc_driver depends on a lot of other crates, but the main one is
    rustc_interface.
  3. rustc_interface depends on most of the other compiler crates. It is a
    fairly generic interface for driving the whole compilation.
  4. Most of the other rustc_* crates depend on rustc_middle, which defines
    a lot of central data structures in the compiler.
  5. rustc_middle and most of the other crates depend on a handful of crates
    representing the early parts of the compiler (e.g. the parser), fundamental
    data structures (e.g. Span), or error reporting:
    rustc_data_structures, rustc_span, rustc_errors, etc.

You can see the exact dependencies by running cargo tree,
just like you would for any other Rust package:

console
cargo tree --package rustc_driver

One final thing: src/llvm-project is a submodule for our fork of LLVM.
During bootstrapping, LLVM is built and the compiler/rustc_llvm crate
contains Rust wrappers around LLVM (which is written in C++), so that the
compiler can interface with it.

Most of this book is about the compiler, so we won't have any further
explanation of these crates here.

Big picture

The dependency structure of the compiler is influenced by two main factors:

  1. Organization. The compiler is a huge codebase; it would be an impossibly
    large crate. In part, the dependency structure reflects the code structure
    of the compiler.
  2. Compile-time. By breaking the compiler into multiple crates, we can take
    better advantage of incremental/parallel compilation using cargo. In
    particular, we try to have as few dependencies between crates as possible so
    that we don't have to rebuild as many crates if you change one.

At the very bottom of the dependency tree are a handful of crates that are used
by the whole compiler (e.g. rustc_span). The very early parts of the
compilation process (e.g. parsing and the Abstract Syntax Tree (AST))
depend on only these.

After the AST is constructed and other early analysis is done, the
compiler's query system gets set up. The query system is set up in a
clever way using function pointers. This allows us to break dependencies
between crates, allowing more parallel compilation. The query system is defined
in rustc_middle, so nearly all subsequent parts of the compiler depend on
this crate. It is a really large crate, leading to long compile times. Some
efforts have been made to move stuff out of it with varying success. Another
side-effect is that sometimes related functionality gets scattered across
different crates. For example, linting functionality is found across earlier
parts of the crate, rustc_lint, rustc_middle, and other places.

Ideally there would be fewer, more cohesive crates, with incremental and
parallel compilation making sure compile times stay reasonable. However,
incremental and parallel compilation haven't gotten good enough for that yet,
so breaking things into separate crates has been our solution so far.

At the top of the dependency tree is rustc_driver and rustc_interface
which is an unstable wrapper around the query system helping drive various
stages of compilation. Other consumers of the compiler may use this interface
in different ways (e.g. rustdoc or maybe eventually rust-analyzer). The
rustc_driver crate first parses command line arguments and then uses
rustc_interface to drive the compilation to completion.

rustdoc

The bulk of rustdoc is in librustdoc. However, the rustdoc binary
itself is src/tools/rustdoc, which does nothing except call rustdoc::main.

There is also JavaScript and CSS for the docs in src/tools/rustdoc-js
and src/tools/rustdoc-themes. The type definitions for --output-format=json
are in a separate crate in src/rustdoc-json-types.

You can read more about rustdoc in this chapter.

Tests

The test suite for all of the above is in tests/. You can read more
about the test suite in this chapter.

The test harness is in src/tools/compiletest/.

Build System

There are a number of tools in the repository just for building the compiler,
standard library, rustdoc, etc, along with testing, building a full Rust
distribution, etc.

One of the primary tools is src/bootstrap/. You can read more about
bootstrapping in this chapter. The process may also use other tools
from src/tools/, such as tidy/ or compiletest/.

Standard library

This code is fairly similar to most other Rust crates except that it must be
built in a special way because it can use unstable (nightly) features.
The standard library is sometimes referred to as libstd or the "standard facade".

Other

There are a lot of other things in the rust-lang/rust repo that are related
to building a full Rust distribution. Most of the time you don't need to worry about them.

These include:

  • src/ci: The CI configuration. This actually quite extensive because we
    run a lot of tests on a lot of platforms.
  • src/doc: Various documentation, including submodules for a few books.
  • src/etc: Miscellaneous utilities.
  • And more...

Src Doc Rustc Dev Guide Src Compiler Team (`src/doc/rustc-dev-guide/src/compiler-team.md`)

About the compiler team

NOTE:
There exists much detail about the team on Forge, making most of the following obsolete.

rustc is maintained by the Rust compiler team.
The people who belong to
this team collectively work to track regressions and implement new features.
Members of the Rust compiler team are people who have made significant
contributions to rustc and its design.

Discussion

Currently the compiler team chats in Zulip:

  • Team chat occurs in the t-compiler stream on the Zulip instance
  • There are also a number of other associated Zulip channels,
    such as t-compiler/help, where people can ask for help
    with rustc development, or t-compiler/meetings,
    where the team holds their weekly triage and steering meetings.

Reviewers

If you're interested in figuring out who can answer questions about a
particular part of the compiler, or you'd just like to know who works on what,
check out triagebot.toml's assign section.
It contains a listing of the various parts of the compiler and a list of people
who are reviewers of each part.

Rust compiler meeting

The compiler team has a weekly meeting where we do triage and try to
generally stay on top of new bugs, regressions, and discuss important things in general.
They are held on Zulip.
It works roughly as follows:

  • Announcements, MCPs/FCPs, and WG-check-ins: We share some
    announcements with the rest of the team about important things we want everyone to be aware of.
    We also share the status of MCPs and FCPs and we
    use the opportunity to have a couple of WGs giving us an update about their work.
  • Check for beta and stable nominations: These are nominations of things to
    backport to beta and stable respectively.
    We then look for new cases where the compiler broke previously working code in the wild.
    Regressions are important issues to fix, so it's
    likely that they are tagged as P-critical or P-high; the major
    exception would be bug fixes (though even there we often aim to give
    warnings first
    ).
  • Review P-critical and P-high bugs: P-critical and P-high bugs are
    those that are sufficiently important for us to actively track progress.
    P-critical and P-high bugs should ideally always have an assignee.
  • Check S-waiting-on-t-compiler and I-compiler-nominated issues: These are issues where
    feedback from the team is desired.
  • Look over the performance triage report: We check for PRs that made the
    performance worse and try to decide if it's worth reverting the performance regression or if
    the regression can be addressed in a future PR.

The meeting currently takes place on Thursdays at 10am Boston time
(UTC-4 typically, but daylight savings time sometimes makes things complicated).

Team membership

Membership in the Rust team is typically offered when someone has been
making significant contributions to the compiler for some time.
Membership is both a recognition but also an obligation:
compiler team members are generally expected to help with upkeep as
well as doing reviews and other work.

If you are interested in becoming a compiler team member, the first
thing to do is to start fixing some bugs, or get involved in a working group.
One good way to find bugs is to look for
open issues tagged with E-easy
or E-mentor.

You can also dig through the graveyard of PRs that were
closed due to inactivity,
some of them may contain work that is still useful - refer to the
associated issues, if any - and only needs some finishing touches
for which the original author didn't have time.

r+ rights

Once you have made a number of individual PRs to rustc, we will often offer r+ privileges.
This means that you have the right to instruct
"bors" (the robot that manages which PRs get landed into rustc) to merge a PR
(here are some instructions for how to talk to bors).

The guidelines for reviewers are as follows:

  • You are always welcome to review any PR, regardless of who it is assigned to.
    However, do not r+ PRs unless:
    • You are confident in that part of the code.
    • You are confident that nobody else wants to review it first.
      • For example, sometimes people will express a desire to review a
        PR before it lands, perhaps because it touches a particularly
        sensitive part of the code.
  • Always be polite when reviewing: you are a representative of the
    Rust project, so it is expected that you will go above and beyond
    when it comes to the Code of Conduct.

Reviewer rotation

Once you have r+ rights, you can also be added to the reviewer rotation.
triagebot is the bot that automatically assigns incoming PRs to reviewers.
If you are added, you will be randomly selected to review PRs.
If you find you are assigned a PR that you don't feel comfortable
reviewing, you can also leave a comment like r? @so-and-so to assign
to someone else — if you don't know who to request, just write r? @nikomatsakis for reassignment and @nikomatsakis will pick someone for you.

Getting on the reviewer rotation is much appreciated as it lowers the review burden for all of us!
However, if you don't have time to give
people timely feedback on their PRs, it may be better that you don't get on the list.


Src Doc Rustc Dev Guide Src Const Eval (`src/doc/rustc-dev-guide/src/const-eval.md`)

Constant Evaluation

Constant evaluation is the process of computing values at compile time.
For a specific item (constant/static/array length) this happens after the MIR for the
item is borrow-checked and optimized.
In many cases trying to const evaluate an
item will trigger the computation of its MIR for the first time.

Prominent examples are:

  • The initializer of a static
  • Array length
    • needs to be known to reserve stack or heap space
  • Enum variant discriminants
    • needs to be known to prevent two variants from having the same discriminant
  • Patterns
    • need to be known to check for overlapping patterns

Additionally constant evaluation can be used to reduce the workload or binary
size at runtime by precomputing complex operations at compile time and only storing the result.

All uses of constant evaluation can either be categorized as "influencing the type system"
(array lengths, enum variant discriminants, const generic parameters), or as solely being
done to precompute expressions to be used at runtime.

Constant evaluation can be done by calling the const_eval_* functions of TyCtxt.
They're the wrappers of the const_eval query.

  • const_eval_global_id_for_typeck evaluates a constant to a valtree,
    so the result value can be further inspected by the compiler.
  • const_eval_global_id evaluate a constant to an "opaque blob" containing its final value;
    this is only useful for codegen backends and the CTFE evaluator engine itself.
  • eval_static_initializer specifically computes the initial values of a static.
    Statics are special; all other functions do not represent statics correctly
    and have thus assertions preventing their use on statics.

The const_eval_* functions use a ParamEnv of environment
in which the constant is evaluated (e.g. the function within which the constant is used)
and a GlobalId.
The GlobalId is made up of an Instance referring to a constant
or static or of an Instance of a function and an index into the function's Promoted table.

Constant evaluation returns an EvalToValTreeResult for type system constants
or EvalToConstValueResult with either the error, or a representation of the
evaluated constant: a valtree or a MIR constant
value
, respectively.


Src Doc Rustc Dev Guide Src Const Generics (`src/doc/rustc-dev-guide/src/const-generics.md`)

Const generics

Kinds of const arguments

Most of the kinds of ty::Const that exist have direct parallels to kinds of types that exist, for example ConstKind::Param is equivalent to TyKind::Param.

The main interesting points here are:

  • ConstKind::Alias, which is equivalent to TyKind::Alias.
  • ConstKind::Value, which is the final value of a ty::Const after monomorphization.
    This is somewhat similar to fully concrete things like TyKind::Str or TyKind::ADT.

For a complete list of all kinds of const arguments and how they are actually represented in the type system, see the ConstKind type.

Inference Variables are quite boring and treated equivalently to type inference variables almost everywhere.
Const Parameters are also similarly boring and equivalent to uses of type parameters almost everywhere.
However, there are some interesting subtleties with how they are handled during parsing, name resolution, and AST lowering: ambig-unambig-ty-and-consts.

Anon consts

Anon consts (short for anonymous const items) are how arbitrary expression are represented in const generics, for example an array length of 1 + 1 or foo() or even just 0.
These are unique to const generics and have no real type equivalent.

Desugaring

rust
struct Foo<const N: usize>;
type Alias = [u8; 1 + 1];

In this example we have a const argument of 1 + 1 (the array length) which is represented as an anon const.
The desugaring would look something like:

rust
struct Foo<const N: usize>;

const ANON: usize = 1 + 1;
type Alias = [u8; ANON];

Where the array length in [u8; ANON] isn't itself an anon const containing a usage of ANON, but a kind of "direct" usage of the ANON const item (ConstKind::Alias).

Anon consts do not inherit any generic parameters of the item they are inside of:

rust
struct Foo<const N: usize>;
type Alias<T: Sized> = [T; 1 + 1];

// Desugars To;

struct Foo<const N: usize>;

const ANON: usize = 1 + 1;
type Alias<T: Sized> = [T; ANON];

Note how the ANON const has no generic parameters or where clauses, even though Alias has both a type parameter T and a where clauses T: Sized.
This desugaring is part of how we enforce that anon consts can't make use of generic parameters.

While it's useful to think of anon consts as being desugared to real const items, the compiler does not actually implement things this way.

At AST lowering time we do not yet know the type of the anon const, so we can't desugar to a real HIR item with an explicitly written type.
To work around this, we have DefKind::AnonConst and hir::Node::AnonConst,
which are used to represent these anonymous const items that can't actually be desugared.

The types of these anon consts are obtainable from the type_of query.
However, the type_of query does not actually contain logic for computing the type (and, in fact, it just ICEs when called).
Instead, HIR Ty lowering is responsible for feeding the value of the type_of query for any anon consts that get lowered.
HIR Ty lowering can determine the type of the anon const by looking at the type of the Const Parameter that the anon const is an argument to.

TODO: write a chapter on query feeding and link it here

In some sense the desugarings from the previous examples are to:

rust
struct Foo<const N: usize>;
type Alias = [u8; 1 + 1];

// sort-of desugars to pseudo-rust:
struct Foo<const N: usize>;

const ANON = 1 + 1;
type Alias = [u8; ANON];

When we go through HIR ty lowering for the array type in Alias, we will lower the array length too, and feed type_of(ANON) -> usize.
This will effectively set the type of the ANON const item during some later part of the compiler rather than when constructing the HIR.

After all of this desugaring has taken place the final representation in the type system (ie as a ty::Const) is a ConstKind::Alias with the DefId of the AnonConst.
This is equivalent to how we would represent a usage of an actual const item if we were to represent them without going through an anon const (e.g. when gca_generic_const_args is enabled).

This allows the representation for const "aliases" to be the same as the representation of TyKind::Alias.
Having a proper HIR body also allows for a lot of code re-use, e.g. we can reuse HIR typechecking and all of the lowering steps to MIR where we can then reuse const eval.

Enforcing lack of generic parameters

There are three ways that we enforce anon consts can't use generic parameters:

  1. Name Resolution will not resolve paths to generic parameters when inside of an anon const
  2. HIR Ty lowering will error when a Self type alias to a type referencing generic parameters is encountered inside of an anon const
  3. Anon Consts do not inherit where clauses or generics from their parent definition (ie generics_of does not contain a parent for anon consts)
rust
// *1* Errors in name resolution
type Alias<const N: usize> = [u8; N + 1];
//~^ ERROR: generic parameters may not be used in const operations

// *2* Errors in HIR Ty lowering:
struct Foo<T>(T);
impl<T> Foo<T> {
    fn assoc() -> [u8; { let a: Self; 0 }] {}
    //~^ ERROR: generic `Self` types are currently not permitted in anonymous constants
}

// *3* Errors due to lack of where clauses on the desugared anon const
trait Trait<T> {
    const ASSOC: usize;
}
fn foo<T>() -> [u8; <()>::ASSOC]
//~^ ERROR: no associated item named `ASSOC` found for unit type `()`
where
    (): Trait<T> {}

The second point is particularly subtle as it is very easy to get HIR Ty lowering wrong and not properly enforce that anon consts can't use generic parameters.
The existing check is too conservative and accidentally permits some generic parameters to wind up in the body of the anon const #144547.

Erroneously allowing generic parameters in anon consts can sometimes lead to ICEs but can also lead to accepting illformed programs.

The third point is also somewhat subtle, by not inheriting any of the where clauses of the parent item we can't wind up with the trait solving inferring inference variables to generic parameters based off where clauses in scope that mention generic parameters.
For example, inferring ?x=T from the expression <() as Trait<?x>>::ASSOC and an in-scope where clause of (): Trait<T>.

This also makes it much more likely that the compiler will ICE or atleast incidentally emit some kind of error if we do accidentally allow generic parameters in an anon const, as the anon const will have none of the necessary information in its environment to properly handle the generic parameters.

Array repeat expressions

The one exception to all of the above is repeat counts of array expressions.
As a backwards compatibility hack, we allow the repeat count const argument to use generic parameters.

rust
fn foo<T: Sized>() {
    let a = [1_u8; size_of::<T>()];
}

However, to avoid most of the problems involved in allowing generic parameters in anon const const arguments,
we require that the constant be evaluated before monomorphization (e.g. during type checking).
In some sense we only allow generic parameters here when they are semantically unused.

In the previous example the anon const can be evaluated for any type parameter T because raw pointers to sized types always have the same size (e.g. 8 on 64bit platforms).

When detecting that we evaluated an anon const that syntactically contained generic parameters, but did not actually depend on them for evaluation to succeed, we emit the const_evaluatable_unchecked FCW.
This is intended to become a hard error once we stabilize more ways of using generic parameters in const arguments, for example gca_min_const_items or (the now dead) generic_const_exprs.

The implementation for this FCW can be found here: const_eval_resolve_for_typeck

Incompatibilities with `generic_const_parameter_types`

Supporting const parameters such as const N: [u8; M] or const N: Foo<T> does not work very nicely with the current anon consts setup.
There are two reasons for this:

  1. As anon consts cannot use generic parameters, their type also can't reference generic parameters.
    This means it is fundamentally not possible to use an anon const as an argument to a const parameter whose type still references generic parameters.

    rust
    #![feature(adt_const_params, generic_const_parameter_types)]
    
    fn foo<const N: usize, const M: [u8; N]>() {}
    
    fn bar<const N: usize>() {
        // There is no way to specify the const argument to `M`
        foo::<N, { [1_u8; N] }>();
    }
  2. We currently require knowing the type of anon consts when lowering them during HIR ty lowering.
    With generic const parameter types it may be the case that the currently known type contains inference variables (ie may not be fully known yet).

    rust
    #![feature(adt_const_params, generic_const_parameter_types)]
    
    fn foo<const N: usize, const M: [u8; N]>() {}
    
    fn bar() {
        // The const argument to `N` must be explicitly specified
        // even though it is able to be inferred
        foo::<_, { [1_u8; 3] }>();
    }

It is currently unclear what the right way to make generic_const_parameter_types work nicely with the rest of const generics is.

generic_const_exprs would have allowed for anon consts with types referencing generic parameters, but that design wound up unworkable.

gca_min_const_items will allow for some expressions (for example array construction) to be representable without an anon const and therefore without running into these issues, though whether this is enough has yet to be determined.

Checking types of const arguments

In order for a const argument to be well formed it must have the same type as the const parameter it is an argument to.
For example, a const argument of type bool for an array length is not well formed, as an array's length parameter has type usize.

rust
type Alias<const B: bool> = [u8; B];
//~^ ERROR:

To check this, we have ClauseKind::ConstArgHasType(ty::Const, Ty), where,
for each Const Parameter defined on an item,
we also desugar an equivalent ConstArgHasType clause into its list of where cluases.
This ensures that whenever we check wellformedness of anything by proving all of its clauses,
we also check happen to check that all of the Const Arguments have the correct type.

rust
fn foo<const N: usize>() {}

// desugars to in pseudo-rust

fn foo<const N>()
where
//  ConstArgHasType(N, usize)
    N: usize, {}

Proving ConstArgHasType goals is implemented by first computing the type of the const argument, then equating it with the provided type.
A rough outline of how the type of a Const Argument may be computed:

  • ConstKind::Param(N) can be looked up in the ParamEnv to find a ConstArgHasType(N, ty) clause
  • ConstKind::Value stores the type of the value inside itself so can trivially be accessed
  • ConstKind::Alias can have its type computed by calling the type_of query
  • See the implementation of proving ConstArgHasType goals for more detailed information

ConstArgHasType is the soundness critical way that we check Const Arguments have the correct type.
However, we do indirectly check the types of Const Arguments a different way in some cases.

rust
type Alias = [u8; true];

// desugars to

const ANON: usize = true;
type Alias = [u8; ANON];

By feeding the type of an anon const with the type of the Const Parameter,
we guarantee that the ConstArgHasType goal involving the anon const will succeed.
In cases where the type of the anon const doesn't match the type of the Const Parameter,
what actually happens is a type checking error when type checking the anon const's body.

Looking at the above example, this corresponds to [u8; ANON] being a well formed type because ANON has type usize, but the body of ANON being illformed and resulting in a type checking error because true can't be returned from a const item of type usize.


Src Doc Rustc Dev Guide Src Contributing To Guide (`src/doc/rustc-dev-guide/src/contributing-to-guide.md`)

Writing rustc-dev-guide documentation

Contributions to the rustc-dev-guide are always welcome, and can be made directly at
the rust-lang/rustc-dev-guide repo.
The issue tracker in that repo is also a great way to find things that need doing.
There are issues for beginners and advanced compiler devs alike!

Just a few things to keep in mind:

  • When writing about a particular part of the compiler's code, we
    recommend that you link to the relevant parts of the rustc API docs.

  • Use sentence case for chapter and section titles.

  • Use dashes (-) to separate words in file names.

  • Links within the guide should use .md relative links, not .html links.
    CI will enforce this.

  • Please try to avoid overly long lines and use semantic line breaks (where you break the line after each sentence).
    This makes it easier to review diffs, since they avoid reflowing other unrelated prose.
    There is no strict limit on line lengths;
    let the sentence or part of the sentence flow to its proper end on the same line.

    You can use a tool in ci/sembr to help with this.
    Its help output can be seen with this command:

    console
    cargo run --manifest-path ci/sembr/Cargo.toml -- --help
  • When contributing text to the guide, please contextualize the information with some time period
    and/or a reason so that the reader knows how much to trust the information.
    Aim to provide a reasonable amount of context, and consider including:

    • A reason for why the text may be out of date other than "change",
      as change is a constant across the project.

    • The date the comment was added, e.g. instead of writing "Currently, ..."
      or "As of now, ...", consider adding the date, in one of the following formats:

      • Jan 2021
      • January 2021
      • jan 2021
      • january 2021

      There is a CI action (in .github/workflows/date-check.yml)
      that generates a monthly report showing those that are over 6 months old
      (example).

      For the action to pick the date, add a special annotation before specifying the date:

      md
      Jul 2026

      Example:

      md
      As of  Jul 2026, the foo did the bar.

      For cases where the date should not be part of the visible rendered output,
      use the following instead:

      md
    • A link to a relevant WG, tracking issue, rustc rustdoc page, or similar, that may provide
      further explanation for the change process or a way to verify that the information is not
      outdated.

⚠️ Note: Where to contribute `rustc-dev-guide` changes

For detailed information about where to contribute rustc-dev-guide changes and the benefits of doing so,
see the rustc-dev-guide team documentation.


Src Doc Rustc Dev Guide Src Contributing (`src/doc/rustc-dev-guide/src/contributing.md`)

Contribution procedures

Bug reports

While bugs are unfortunate, they're a reality in software.
We can't fix what we don't know about, so please report liberally.
If you're not sure if something is a bug, feel free to open an issue anyway.

If you believe reporting your bug publicly represents a security risk to Rust users,
please follow our instructions for reporting security vulnerabilities
.

If you're using the nightly channel, please check if the bug exists in the
latest toolchain before filing your bug.
It might be fixed already.

If you have the chance, before reporting a bug, please search existing issues,
as it's possible that someone else has already reported your error.
This doesn't always work, and sometimes it's hard to know what to search for, so consider this
extra credit.
We won't mind if you accidentally file a duplicate report.

Similarly, to help others who encountered the bug find your issue, consider
filing an issue with a descriptive title, which contains information that might be unique to it.
This can be the language or compiler feature used, the
conditions that trigger the bug, or part of the error message if there is any.
An example could be: "impossible case reached" on lifetime inference for impl
Trait in return position
.

Opening an issue is as easy as following this link and filling out the fields
in the appropriate provided template.

Other procedures

Other contribution procedures are documented with the parts of the guide that own them:





Src Doc Rustc Dev Guide Src Conventions (`src/doc/rustc-dev-guide/src/conventions.md`)

Coding conventions

This chapter covers formatting, coding for correctness,
using crates from crates.io, and some tips on structuring your PR for easy review.

Formatting and the tidy script

rustc is moving towards the Rust standard coding style.

However, for now we don't use stable rustfmt; we use a pinned version with a
special config, so this may result in different style from normal rustfmt.
Therefore, formatting this repository using cargo fmt is not recommended.

Instead, formatting should be done using ./x fmt.
It's a good habit to run ./x fmt before every commit, as this reduces conflicts later.

Formatting is checked by the tidy script.
It runs automatically when you do ./x test and can be run in isolation with ./x fmt --check.

Note: Formatting and test suites

Most Rust source files under tests/ directory are not formatted for reasons
such as whitespace sensitivity, nature of snapshot tests, location-sensitive
comments and more.

Consult the ignore entries in
https://github.com/rust-lang/rust/blob/main/rustfmt.toml for which test
files are not formatted.

If you want to use format-on-save in your editor, the pinned version of
rustfmt is built under build/<target>/stage0/bin/rustfmt.

Formatting C++ code

The compiler contains some C++ code for interfacing with parts of LLVM that
don't have a stable C API.
When modifying that code, use this command to format it:

console
./x test tidy --extra-checks cpp:fmt --bless

This uses a pinned version of clang-format, to avoid relying on the local environment.

Formatting and linting Python code

The Rust repository contains quite a lot of Python code.
We try to keep it both linted and formatted by the ruff tool.

When modifying Python code, use this command to format it:

console
./x test tidy --extra-checks py:fmt --bless

And, the following command to run lints:

console
./x test tidy --extra-checks py:lint

These use a pinned version of ruff, to avoid relying on the local environment.

In the past, files began with a copyright and license notice.
Please omit this notice for new files licensed under the standard terms (MIT OR Apache-2.0).

All of the copyright notices should be gone by now, but if you come across one
in the rust-lang/rust repo, feel free to open a PR to remove it.

Line length

Lines should be at most 100 characters.
It's even better if you can keep things to 80.

Sometimes, and particularly for tests, it can be necessary to exempt yourself from this limit.
In that case, you can add a comment towards the top of the file like so:

rust
// ignore-tidy-linelength

Tabs vs spaces

Prefer 4-space indents.

Coding for correctness

Beyond formatting, there are a few other tips that are worth following.

Prefer exhaustive matches

Using _ in a match is convenient, but it means that when new
variants are added to the enum, they may not get handled correctly.
Ask yourself: if a new variant were added to this enum, what's the
chance that it would want to use the _ code, versus having some other treatment?
Unless the answer is "low", then prefer an exhaustive match.

The same advice applies to if let and while let,
which are effectively tests for a single variant.

Use "TODO" comments for things you don't want to forget

As a useful tool to yourself, you can insert a // TODO comment
for something that you want to get back to before you land your PR:

rust,ignore
fn do_something() {
    if something_else {
        unimplemented!(); // TODO write this
    }
}

The tidy script will report an error for a // TODO comment, so this
code would not be able to land until the TODO is fixed (or removed).

This can also be useful in a PR as a way to signal from one commit that you are
leaving a bug that a later commit will fix:

rust,ignore
if foo {
    return true; // TODO wrong, but will be fixed in a later commit
}

If you want to leave a note in the codebase, use // FIXME instead.

Follow the style of surrounding code

Use existing helpers and avoid duplicating logic or validation.

[Avoid duplicated sources of truth](https://react.dev/learn/choosing-the-state-structure)

Trying to keep data in sync between two different places is a code smell.

Use types to enforce invariants

When practical, make invalid states unrepresentable, not just checked at construction time.

Write useful comments

Write comments that say why you have done a thing, not what you have done.
It's ok to go into detail about non-obvious bugs.

Preserve existing behavior

Consider platform differences and error cases.
Look for relevant tests that exercise the edge cases.

Work in small steps

Work in small, independently testable steps.
Run the relevant tests after every meaningful change, so you know where you first went wrong.

Using crates from crates.io

See the crates.io dependencies section.

How to structure your PR

How you prepare the commits in your PR can make a big difference for the reviewer.
Here are some tips.

Isolate "pure refactorings" into their own commit. For example, if
you rename a method, then put that rename into its own commit, along
with the renames of all the uses.

More commits is usually better. If you are doing a large change,
it's almost always better to break it up into smaller steps that can be independently understood.
The one thing to be aware of is that if
you introduce some code following one strategy, then change it
dramatically (versus adding to it) in a later commit, that 'back-and-forth' can be confusing.

Format liberally. While only the final commit of a PR must be correctly
formatted, it is both easier to review and less noisy to format each commit
individually using ./x fmt.

No merges. We do not allow merge commits into our history, other than those by bors.
If you get a merge conflict, rebase instead via a
command like git rebase --interactive rust-lang/main (presuming you use the
name rust-lang for your remote).

Individual commits do not have to build (but it's nice). We do not
require that every intermediate commit successfully builds – we only
expect to be able to bisect at a PR level.
However, if you can make individual commits build, that is always helpful.

Naming conventions

Apart from normal Rust style/naming conventions, there are also some specific to the compiler.

  • cx tends to be short for "context" and is often used as a suffix.
    For example, tcx is a common name for the Typing Context.

  • 'tcx is used as the lifetime name for the Typing Context.

  • Because crate is a keyword, if you need a variable to represent something
    crate-related, often the spelling is changed to krate.


Src Doc Rustc Dev Guide Src Coroutine Closures (`src/doc/rustc-dev-guide/src/coroutine-closures.md`)

Async closures/"coroutine-closures"

Please read RFC 3668 to understand the general motivation of the feature.
This is a very technical and somewhat "vertical" chapter;
ideally, we'd split this and sprinkle it across all the relevant chapters,
but for the purposes of understanding async closures holistically,
I've put this together all here in one chapter.

Coroutine-closures -- a technical deep dive

Coroutine-closures are a generalization of async closures, being special syntax for closure expressions which return a coroutine, notably one that is allowed to capture from the closure's upvars.

For now, the only usable kind of coroutine-closure is the async closure, and supporting async closures is the extent of this PR. We may eventually support gen || {}, etc., and most of the problems and curiosities described in this document apply to all coroutine-closures in general.

As a consequence of the code being somewhat general, this document may flip between calling them "async closures" and "coroutine-closures".
The future that is returned by the async closure will generally be called the "coroutine" or the "child coroutine".

HIR

Async closures (and in the future, other coroutine flavors such as gen) are represented in HIR as a hir::Closure.
The closure-kind of the hir::Closure is ClosureKind::CoroutineClosure(_)^k1, which wraps an async block, which is also represented in HIR as a hir::Closure.
The closure-kind of the async block is ClosureKind::Closure(CoroutineKind::Desugared(_, CoroutineSource::Closure))^k2.

Like async fn, when lowering an async closure's body, we need to unconditionally move all of the closures arguments into the body so they are captured.
This is handled by lower_coroutine_body_with_moved_arguments^l1.
The only notable quirk with this function is that the async block we end up generating as a capture kind of CaptureBy::ByRef^l2.
We later force all of the closure args to be captured by-value^l3, but we don't want the whole async block to act as if it were an async move, since that would defeat the purpose of the self-borrowing of an async closure.

`rustc_middle::ty` representation

For the purposes of keeping the implementation mostly future-compatible (i.e. with gen || {} and async gen || {}), most of this section calls async closures "coroutine-closures".

The main thing that this PR introduces is a new TyKind called CoroutineClosure^t1 and corresponding variants on other relevant enums in typeck and borrowck (UpvarArgs, DefiningTy, AggregateKind).

We introduce a new TyKind instead of generalizing the existing TyKind::Closure due to major representational differences in the type.
The major differences between CoroutineClosures can be explored by first inspecting the CoroutineClosureArgsParts, which is the "unpacked" representation of the coroutine-closure's generics.

Similarities to closures

Like a closure, we have parent_args, a closure_kind_ty, and a tupled_upvars_ty. These represent the same thing as their closure counterparts; namely: the generics inherited from the body that the closure is defined in, the maximum "calling capability" of the closure (i.e. must it be consumed to be called, like FnOnce, or can it be called by-ref), and the captured upvars of the closure itself.

The signature

A traditional closure has a fn_sig_as_fn_ptr_ty which it uses to represent the signature of the closure.
In contrast, we store the signature of a coroutine closure in a somewhat "exploded" way, since coroutine-closures have two signatures depending on what AsyncFn* trait you call it with (see below sections).

Conceptually, the coroutine-closure may be thought as containing several different signature types depending on whether it is being called by-ref or by-move.

To conveniently recreate both of these signatures, the signature_parts_ty stores all of the relevant parts of the coroutine returned by this coroutine-closure.
This signature parts type will have the general shape of fn(tupled_inputs, resume_ty) -> (return_ty, yield_ty), where resume_ty, return_ty, and yield_ty are the respective types for the coroutine returned by the coroutine-closure^c1.

The compiler mainly deals with the CoroutineClosureSignature type^c2, which is created by extracting the relevant types out of the fn() ptr type described above, and which exposes methods that can be used to construct the coroutine that the coroutine-closure ultimately returns.

The data we need to carry along to construct a `Coroutine` return type

Along with the data stored in the signature, to construct a TyKind::Coroutine to return, we also need to store the "witness" of the coroutine.

So what about the upvars of the Coroutine that is returned? Well, for AsyncFnOnce (i.e. call-by-move), this is simply the same upvars that the coroutine returns. But for AsyncFnMut/AsyncFn, the coroutine that is returned from the coroutine-closure borrows data from the coroutine-closure with a given "environment" lifetime^c3. This corresponds to the &self lifetime^c4 on the AsyncFnMut/AsyncFn call signature, and the GAT lifetime of the ByRef^c5.

Actually getting the coroutine return type(s)

To most easily construct the Coroutine that a coroutine-closure returns, you can use the to_coroutine_given_kind_and_upvars^helper helper on CoroutineClosureSignature, which can be acquired from the CoroutineClosureArgs.

Most of the args to that function will be components that you can get out of the CoroutineArgs, except for the goal_kind: ClosureKind which controls which flavor of coroutine to return based off of the ClosureKind passed in -- i.e. it will prepare the by-ref coroutine if ClosureKind::Fn | ClosureKind::FnMut, and the by-move coroutine if ClosureKind::FnOnce.

Trait hierarchy

We introduce a parallel hierarchy of Fn* traits that are implemented for .
The motivation for the introduction was covered in a blog post: Async Closures.

All currently-stable callable types (i.e., closures, function items, function pointers, and dyn Fn* trait objects) automatically implement AsyncFn*() -> T if they implement Fn*() -> Fut for some output type Fut, and Fut implements Future<Output = T>^tr1.

Async closures implement AsyncFn* as their bodies permit; i.e. if they end up using upvars in a way that is compatible (i.e. if they consume or mutate their upvars, it may affect whether they implement AsyncFn and AsyncFnMut...)

Lending

We may in the future move AsyncFn* onto a more general set of LendingFn* traits; however, there are some concrete technical implementation details that limit our ability to use LendingFn ergonomically in the compiler today.
These have to do with:

  • Closure signature inference.
  • Limitations around higher-ranked trait bounds.
  • Shortcomings with error messages.

These limitations, plus the fact that the underlying trait should have no effect on the user experience of async closures and async Fn trait bounds, leads us to AsyncFn* for now.
To ensure we can eventually move to these more general traits, the precise AsyncFn* trait definitions (including the associated types) are left as an implementation detail.

When do async closures implement the regular `Fn*` traits?

We mention above that "regular" callable types can implement AsyncFn*, but the reverse question exists of "can async closures implement Fn* too"? The short answer is "when it's valid", i.e. when the coroutine that would have been returned from AsyncFn/AsyncFnMut does not actually have any upvars that are "lent" from the parent coroutine-closure.

See the "follow-up: when do..." section below for an elaborated answer.
The full answer describes a pretty interesting and hopefully thorough heuristic that is used to ensure that most async closures "just work".

Tale of two bodies...

When async closures are called with AsyncFn/AsyncFnMut, they return a coroutine that borrows from the closure.
However, when they are called via AsyncFnOnce, we consume that closure, and cannot return a coroutine that borrows from data that is now dropped.

To work around this limitation, we synthesize a separate by-move MIR body for calling AsyncFnOnce::call_once on a coroutine-closure that can be called by-ref.

This body operates identically to the "normal" coroutine returned from calling the coroutine-closure, except for the fact that it has a different set of upvars, since we must move the captures from the parent coroutine-closure into the child coroutine.

Synthesizing the by-move body

When we want to access the by-move body of the coroutine returned by a coroutine-closure, we can do so via the coroutine_by_move_body_def_id^b1 query.

This query synthesizes a new MIR body by copying the MIR body of the coroutine and inserting additional derefs and field projections^b2 to preserve the semantics of the body.

Since we've synthesized a new def id, this query is also responsible for feeding a ton of other relevant queries for the MIR body.
This query is ensure()d^b3 during the mir_promoted query, since it operates on the built mir of the coroutine.

Closure signature inference

The closure signature inference algorithm for async closures is a bit more complicated than the inference algorithm for "traditional" closures.
Like closures, we iterate through all of the clauses that may be relevant (for the expectation type passed in)^deduce1.

To extract a signature, we consider two situations:

  • Projection predicates with AsyncFnOnce::Output, which we will use to extract the inputs and output type for the closure.
    This corresponds to the situation that there was a F: AsyncFn*() -> T bound^deduce2.
  • Projection predicates with FnOnce::Output, which we will use to extract the inputs.
    For the output, we also try to deduce an output by looking for relevant Future::Output projection predicates.
    This corresponds to the situation that there was an F: Fn*() -> T, T: Future<Output = U> bound.^deduce3
    • If there is no Future bound, we simply use a fresh infer var for the output.
      This corresponds to the case where one can pass an async closure to a combinator function like Option::map.^deduce4

We support the latter case simply to make it easier for users to simply drop-in async || {} syntax, even when they're calling an API that was designed before first-class AsyncFn* traits were available.

Calling a closure before its kind has been inferred

We defer^call1 the computation of a coroutine-closure's "kind" (i.e. its maximum calling mode: AsyncFnOnce/AsyncFnMut/AsyncFn) until the end of typeck. However, since we want to be able to call that coroutine-closure before the end of typeck, we need to come up with the return type of the coroutine-closure before that.

Unlike regular closures, whose return type does not change depending on what Fn* trait we call it with, coroutine-closures do end up returning different coroutine types depending on the flavor of AsyncFn* trait used to call it.

Specifically, while the def-id of the returned coroutine does not change, the upvars^call2 (which are either borrowed or moved from the parent coroutine-closure) and the coroutine-kind^call3 are dependent on the calling mode.

We introduce a AsyncFnKindHelper trait which allows us to defer the question of "does this coroutine-closure support this calling mode"^helper1 via a trait goal, and "what are the tupled upvars of this calling mode"^helper2 via an associated type, which can be computed by appending the input types of the coroutine-closure to either the upvars or the "by ref" upvars computed during upvar analysis.

Ok, so why?

This seems a bit roundabout and complex, and I admit that it is.
But let's think of the "do nothing" alternative -- we could instead mark all AsyncFn* goals as ambiguous until upvar analysis, at which point we would know exactly what to put into the upvars of the coroutine we return.
However, this is actually very detrimental to inference in the program, since it means that programs like this would not be valid:

rust,ignore
let c = async || -> String { .. };
let s = c().await;
// ^^^ If we can't project `<{c} as AsyncFn>::call()` to a coroutine, then the `IntoFuture::into_future` call inside of the `.await` stalls, and the type of `s` is left unconstrained as an infer var.
s.as_bytes();
// ^^^ That means we can't call any methods on the awaited return of a coroutine-closure, like... at all!

So instead, we use this alias (in this case, a projection: AsyncFnKindHelper::Upvars<'env, ...>) to delay the computation of the tupled upvars and give us something to put in its place, while still allowing us to return a TyKind::Coroutine (which is a rigid type) and we may successfully confirm the built-in traits we need (in our case, Future), since the Future implementation doesn't depend on the upvars at all.

Upvar analysis

By and large, the upvar analysis for coroutine-closures and their child coroutines proceeds like normal upvar analysis.
However, there are several interesting bits that happen to account for async closures' special natures:

Forcing all inputs to be captured

Like async fn, all input arguments are captured.
We explicitly force^f1 all of these inputs to be captured by move so that the future coroutine returned by async closures does not depend on whether the input is used by the body or not, which would impart an interesting semver hazard.

Computing the by-ref captures

For a coroutine-closure that supports AsyncFn/AsyncFnMut, we must also compute the relationship between the captures of the coroutine-closure and its child coroutine.
Specifically, the coroutine-closure may move a upvar into its captures, but the coroutine may only borrow that upvar.

We compute the "coroutine_captures_by_ref_ty" by looking at all of the child coroutine's captures and comparing them to the corresponding capture of the parent coroutine-closure^br1.
This coroutine_captures_by_ref_ty ends up being represented as a for<'env> fn() -> captures... type, with the additional binder lifetime representing the "&self" lifetime of calling AsyncFn::async_call or AsyncFnMut::async_call_mut.
We instantiate that binder later when actually calling the methods.

Note that not every by-ref capture from the parent coroutine-closure results in a "lending" borrow.
See the Follow-up: When do async closures implement the regular Fn* traits? section below for more details, since this intimately influences whether or not the coroutine-closure is allowed to implement the Fn* family of traits.

By-move body + `FnOnce` quirk

There are several situations where the closure upvar analysis ends up inferring upvars for the coroutine-closure's child coroutine that are too relaxed, and end up resulting in borrow-checker errors.
This is best illustrated via examples.
For example, given:

rust
fn force_fnonce<T: async FnOnce()>(t: T) -> T { t }

let x = String::new();
let c = force_fnonce(async move || {
    println!("{x}");
});

x will be moved into the coroutine-closure, but the coroutine that is returned would only borrow &x.
However, since force_fnonce forces the coroutine-closure to AsyncFnOnce, which is not lending, we must force the capture to happen by-move^bm1.

Similarly:

rust
let x = String::new();
let y = String::new();
let c = async move || {
    drop(y);
    println!("{x}");
};

x will be moved into the coroutine-closure, but the coroutine that is returned would only borrow &x. However, since we also capture y and drop it, the coroutine-closure is forced to be AsyncFnOnce. We must also force the capture of x to happen by-move. To determine this situation in particular, since unlike the last example the coroutine-kind's closure-kind has not yet been constrained, we must analyze the body of the coroutine-closure to see if how all of the upvars are used, to determine if they've been used in a way that is "consuming" -- i.e. that would force it to FnOnce^bm2.

Follow-up: When do async closures implement the regular `Fn*` traits?

Well, first of all, all async closures implement FnOnce since they can always be called at least once.

For Fn/FnMut, the detailed answer involves answering a related question: is the coroutine-closure lending?
Because if it is, then it cannot implement the non-lending Fn/FnMut traits.

Determining when the coroutine-closure must lend its upvars is implemented in the should_reborrow_from_env_of_parent_coroutine_closure helper function^u1.
Specifically, this needs to happen in two places:

  1. Are we borrowing data owned by the parent closure?
    We can determine if that is the case by checking if the parent capture is by-move, EXCEPT if we apply a deref projection, which means we're reborrowing a reference that we captured by-move.
rust
let x = &1i32; // Let's call this lifetime `'1`.
let c = async move || {
    println!("{:?}", *x);
    // Even though the inner coroutine borrows by ref, we're only capturing `*x`,
    // not `x`, so the inner closure is allowed to reborrow the data for `'1`.
};
  1. If a coroutine is mutably borrowing from a parent capture, then that mutable borrow cannot live for longer than either the parent or the borrow that we have on the original upvar.
    Therefore we always need to borrow the child capture with the lifetime of the parent coroutine-closure's env.
rust
let mut x = 1i32;
let c = async || {
    x = 1;
    // The parent borrows `x` for some `&'1 mut i32`.
    // However, when we call `c()`, we implicitly autoref for the signature of
    // `AsyncFnMut::async_call_mut`. Let's call that lifetime `'call`. Since
    // the maximum that `&'call mut &'1 mut i32` can be reborrowed is `&'call mut i32`,
    // the inner coroutine should capture w/ the lifetime of the coroutine-closure.
};

If either of these cases apply, then we should capture the borrow with the lifetime of the parent coroutine-closure's env.
Luckily, if this function is not correct, then the program is not unsound, since we still borrowck and validate the choices made from this function -- the only side-effect is that the user may receive unnecessary borrowck errors.

Instance resolution

If a coroutine-closure has a closure-kind of FnOnce, then its AsyncFnOnce::call_once and FnOnce::call_once implementations resolve to the coroutine-closure's body^res1, and the Future::poll of the coroutine that gets returned resolves to the body of the child closure.

If a coroutine-closure has a closure-kind of FnMut/Fn, then the same applies to AsyncFn and the corresponding Future implementation of the coroutine that gets returned.^res1 However, we use a MIR shim to generate the implementation of AsyncFnOnce::call_once/FnOnce::call_once^res2, and Fn::call/FnMut::call_mut instances if they exist^res3.

This is represented by the ConstructCoroutineInClosureShim^i1.
The receiver_by_ref bool will be true if this is the instance of Fn::call/FnMut::call_mut.^i2 The coroutine that all of these instances returns corresponds to the by-move body we will have synthesized by this point.^i3

Borrow-checking

It turns out that borrow-checking async closures is pretty straightforward.
After adding a new DefiningTy::CoroutineClosure^bck1 variant, and teaching borrowck how to generate the signature of the coroutine-closure^bck2, borrowck proceeds totally fine.

One thing to note is that we don't borrow-check the synthetic body we make for by-move coroutines, since by construction (and the validity of the by-ref coroutine body it was derived from) it must be valid.


Src Doc Rustc Dev Guide Src Crates Io (`src/doc/rustc-dev-guide/src/crates-io.md`)

crates.io dependencies

The Rust compiler supports building with some dependencies from crates.io.

Rust Forge has official policy for vetting new dependencies.

Permitted dependencies

The tidy tool has a list of crates that are allowed.
To add a dependency that is not already in the compiler, you will need to add it to the list.


Src Doc Rustc Dev Guide Src Debugging Support In Rustc (`src/doc/rustc-dev-guide/src/debugging-support-in-rustc.md`)

Debugging support in the Rust compiler

This document explains the state of debugging tools support in the Rust compiler (rustc).
It gives an overview of GDB, LLDB, WinDbg/CDB,
as well as infrastructure around Rust compiler to debug Rust code.
If you want to learn how to debug the Rust compiler itself,
see Debugging the Compiler.

The material is gathered from the video,
Tom Tromey discusses debugging support in rustc.

Preliminaries

Debuggers

According to Wikipedia

A debugger or debugging tool is a computer program that is used to test and debug
other programs (the "target" program).

Writing a debugger from scratch for a language requires a lot of work, especially if
debuggers have to be supported on various platforms.
GDB and LLDB, however, can be extended to support debugging a language.
This is the path that Rust has chosen.
This document's main goal is to document the said debuggers support in Rust compiler.

DWARF

According to the DWARF standard website

DWARF is a debugging file format used by many compilers and debuggers to support source level
debugging. It addresses the requirements of a number of procedural languages,
such as C, C++, and Fortran, and is designed to be extensible to other languages.
DWARF is architecture independent and applicable to any processor or operating system.
It is widely used on Unix, Linux and other operating systems,
as well as in stand-alone environments.

DWARF reader is a program that consumes the DWARF format and creates debugger compatible output.
This program may live in the compiler itself.
DWARF uses a data structure called
Debugging Information Entry (DIE) which stores the information as "tags" to denote functions,
variables etc., e.g., DW_TAG_variable, DW_TAG_pointer_type, DW_TAG_subprogram etc.
You can also invent your own tags and attributes.

CodeView/PDB

PDB (Program Database) is a file format created by Microsoft that contains debug information.
PDBs can be consumed by debuggers such as WinDbg/CDB and other tools to display debug information.
A PDB contains multiple streams that describe debug information about a specific binary such
as types, symbols, and source files used to compile the given binary.
CodeView is another
format which defines the structure of symbol records and type records that appear within
PDB streams.

Supported debuggers

GDB

Rust expression parser

To be able to show debug output, we need an expression parser.
This (GDB) expression parser is written in Bison,
and can parse only a subset of Rust expressions.
GDB parser was written from scratch and has no relation to any other parser,
including that of rustc.

GDB has Rust-like value and type output.
It can print values and types in a way that look like Rust syntax in the output.
Or when you print a type as ptype in GDB,
it also looks like Rust source code.
Checkout the documentation in the manual for GDB/Rust.

Parser extensions

Expression parser has a couple of extensions in it to facilitate features that you cannot do
with Rust.
Some limitations are listed in the manual for GDB/Rust.
There is some special code in the DWARF reader in GDB to support the extensions.

A couple of examples of DWARF reader support needed are as follows:

  1. Enum: Needed for support for enum types.
    The Rust compiler writes the information about enum into DWARF,
    and GDB reads the DWARF to understand where is the tag field,
    or if there is a tag field,
    or if the tag slot is shared with non-zero optimization etc.

  2. Dissect trait objects: DWARF extension where the trait object's description in the DWARF
    also points to a stub description of the corresponding vtable which in turn points to the
    concrete type for which this trait object exists.
    This means that you can do a print *object
    for that trait object, and GDB will understand how to find the correct type of the payload in
    the trait object.

TODO: Figure out if the following should be mentioned in the GDB-Rust document rather than
this guide page so there is no duplication.
This is regarding the following comments:

This comment by Tom

gdb's Rust extensions and limitations are documented in the gdb manual:
https://sourceware.org/gdb/onlinedocs/gdb/Rust.html -- however, this neglects to mention that
gdb convenience variables and registers follow the gdb $ convention, and that the Rust parser
implements the gdb @ extension.

This question by Aman

@tromey do you think we should mention this part in the GDB-Rust document rather than this
document so there is no duplication etc.?

LLDB

Rust expression parser

This expression parser is written in C++.
It is a type of Recursive Descent parser.
It implements slightly less of the Rust language than GDB.
LLDB has Rust-like value and type output.

Developer notes

  • LLDB has a plugin architecture but that does not work for language support.
  • GDB generally works better on Linux.

WinDbg/CDB

Microsoft provides Windows Debugging Tools such as the Windows Debugger (WinDbg) and
the Console Debugger (CDB) which both support debugging programs written in Rust.
These debuggers parse the debug info for a binary from the PDB, if available, to construct a
visualization to serve up in the debugger.

Natvis

Both WinDbg and CDB support defining and viewing custom visualizations for any given type
within the debugger using the Natvis framework.
The Rust compiler defines a set of Natvis
files that define custom visualizations for a subset of types in the standard libraries such
as, std, core, and alloc.
These Natvis files are embedded into PDBs generated by the
*-pc-windows-msvc target triples to automatically enable these custom visualizations when
debugging.
This default can be overridden by setting the strip rustc flag to either debuginfo or symbols.

Rust has support for embedding Natvis files for crates outside of the standard libraries by
using the #[debugger_visualizer] attribute.
For more details on how to embed debugger visualizers,
please refer to the section on the debugger_visualizer attribute.

DWARF and `rustc`

DWARF is the standard way compilers generate debugging information that debuggers read.
It is the debugging format on macOS and Linux.
It is a multi-language and extensible format,
and is mostly good enough for Rust's purposes.
Hence, the current implementation reuses DWARF's concepts.
This is true even if some of the concepts in DWARF do not align with Rust semantically because,
generally, there can be some kind of mapping between the two.

We have some DWARF extensions that the Rust compiler emits and the debuggers understand that
are not in the DWARF standard.

  • Rust compiler will emit DWARF for a virtual table, and this vtable object will have a
    DW_AT_containing_type that points to the real type.
    This lets debuggers dissect a trait object pointer to correctly find the payload.
    Here is an example of such a DIE, from a test case in the gdb repository:

    asm
    <1><1a9>: Abbrev Number: 3 (DW_TAG_structure_type)
       <1aa>   DW_AT_containing_type: <0x1b4>
       <1ae>   DW_AT_name        : (indirect string, offset: 0x23d): vtable
       <1b2>   DW_AT_byte_size   : 0
       <1b3>   DW_AT_alignment   : 8
  • The other extension is that the Rust compiler can emit a tagless discriminated union.
    See DWARF feature request for this item.

Current limitations of DWARF

  • Traits - require a bigger change than normal to DWARF, on how to represent Traits in DWARF.
  • DWARF provides no way to differentiate between Structs and Tuples.
    Rust compiler emits
    fields with __0 and debuggers look for a sequence of such names to overcome this limitation.
    For example, in this case the debugger would look at a field via x.__0 instead of x.0.
    This is resolved via the Rust parser in the debugger so now you can do x.0.

DWARF relies on debuggers to know some information about platform ABI.
Rust does not do that all the time.

Developer notes

This section is from the talk about certain aspects of development.

What is missing

Code signing for LLDB debug server on macOS

According to Wikipedia, System Integrity Protection is

System Integrity Protection (SIP, sometimes referred to as rootless) is a security feature
of Apple's macOS operating system introduced in OS X El Capitan. It comprises a number of
mechanisms that are enforced by the kernel. A centerpiece is the protection of system-owned
files and directories against modifications by processes without a specific "entitlement",
even when executed by the root user or a user with root privileges (sudo).

It prevents processes using ptrace syscall.
If a process wants to use ptrace it has to be code signed.
The certificate that signs it has to be trusted on your machine.

See Apple developer documentation for System Integrity Protection.

We may need to sign up with Apple and get the keys to do this signing.
Tom has looked into if Mozilla cannot do this because it is at the maximum number of
keys it is allowed to sign.
Tom does not know if Mozilla could get more keys.

Alternatively, Tom suggests that maybe a Rust legal entity is needed to get the keys via Apple.
This problem is not technical in nature.
If we had such a key, we could sign GDB as well and ship that.

DWARF and Traits

Rust traits are not emitted into DWARF at all.
The impact of this is calling a method x.method() does not work as-is.
The reason being that method is implemented by a trait, as opposed to a type.
That information is not present, so finding trait methods is missing.

DWARF has a notion of interface types (possibly added for Java).
Tom's idea was to use this interface type as traits.

DWARF only deals with concrete names, not the reference types.
So, a given implementation of a
trait for a type would be one of these interfaces (DW_tag_interface type).
Also, the type for which it is implemented would describe all the interfaces this type implements.
This requires a DWARF extension.

Issue on GitHub: https://github.com/rust-lang/rust/issues/33014

Typical process for a Debug Info change (LLVM)

LLVM has Debug Info (DI) builders.
This is the primary thing that Rust calls into.
This is why we need to change LLVM first because that is emitted first and not DWARF directly.
This is a kind of metadata that you construct and hand-off to LLVM.
For the Rustc/LLVM hand-off,
some LLVM DI builder methods are called to construct representation of a type.

The steps of this process are as follows:

  1. LLVM needs changing.

    LLVM does not emit Interface types at all, so this needs to be implemented in LLVM first.

    Get sign off on LLVM maintainers that this is a good idea.

  2. Change the DWARF extension.

  3. Update the debuggers.

    Update DWARF readers, expression evaluators.

  4. Update Rust compiler.

    Change it to emit this new information.

Procedural macro stepping

A deeply profound question is that how do you actually debug a procedural macro?
What is the location you emit for a macro expansion?
Consider some of the following cases -

  • You can emit location of the invocation of the macro.
  • You can emit the location of the definition of the macro.
  • You can emit locations of the content of the macro.

RFC: https://github.com/rust-lang/rfcs/pull/2117

Focus is to let macros decide what to do.
This can be achieved by having some kind of attribute
that lets the macro tell the compiler where the line marker should be.
This affects where you set the breakpoints and what happens when you step it.

Source file checksums in debug info

Both DWARF and CodeView (PDB) support embedding a cryptographic hash of each source file that
contributed to the associated binary.

The cryptographic hash can be used by a debugger to verify that the source file matches the
executable.
If the source file does not match, the debugger can provide a warning to the user.

The hash can also be used to prove that a given source file has not been modified since it was
used to compile an executable.
Because MD5 and SHA1 both have demonstrated vulnerabilities,
using SHA256 is recommended for this application.

The Rust compiler stores the hash for each source file in the corresponding SourceFile in
the SourceMap.
The hashes of input files to external crates are stored in rlib metadata.

A default hashing algorithm is set in the target specification.
This allows the target to
specify the best hash available, since not all targets support all hash algorithms.

The hashing algorithm for a target can also be overridden with the -Z source-file-checksum=
command-line option.

DWARF 5

DWARF version 5 supports embedding an MD5 hash to validate the source file version in use.
DWARF 5 - Section 6.2.4.1 opcode DW_LNCT_MD5

LLVM

LLVM IR supports MD5 and SHA1 (and SHA256 in LLVM 11+) source file checksums in the DIFile node.

LLVM DIFile documentation

Microsoft Visual C++ Compiler /ZH option

The MSVC compiler supports embedding MD5, SHA1, or SHA256 hashes in the PDB using the /ZH
compiler option.

MSVC /ZH documentation

Clang

Clang always embeds an MD5 checksum, though this does not appear in documentation.

Future work

Name mangling changes

  • New demangler in libiberty (gcc source tree).
  • New demangler in LLVM or LLDB.

TODO: Check the location of the demangler source.
#1157

Reuse Rust compiler for expressions

This is an important idea because debuggers by and large do not try to implement type inference.
You need to be much more explicit when you type into the debugger than your actual source code.
So, you cannot just copy and paste an expression from your source
code to debugger and expect the same answer, but this would be nice.
This can be helped by using compiler.

It is certainly doable, but it is a large project.
You certainly need a bridge to the debugger because the debugger alone has access to the memory.
Both GDB (gcc) and LLDB (clang) have this feature.
LLDB uses Clang to compile code to JIT and GDB can do the same with GCC.

Both debuggers expression evaluation implement both a superset and a subset of Rust.
They implement just the expression language,
but they also add some extensions like GDB has convenience variables.
Therefore, if you are taking this route,
then you not only need to do this bridge,
but may have to add some mode to let the compiler understand some extensions.


Src Doc Rustc Dev Guide Src Diagnostics (`src/doc/rustc-dev-guide/src/diagnostics.md`)

Errors and lints

A lot of effort has been put into making rustc have great error messages.
This chapter is about how to emit compile errors and lints from the compiler.

Diagnostic structure

The main parts of a diagnostic error are the following:

text
error[E0000]: main error message
  --> file.rs:LL:CC
   |
LL | <code>
   | -^^^^- secondary label
   |  |
   |  primary label
   |
   = note: note without a `Span`, created with `.note`
note: sub-diagnostic message for `.span_note`
  --> file.rs:LL:CC
   |
LL | more code
   |      ^^^^
  • Level (error, warning, etc.). It indicates the severity of the message.
    (See diagnostic levels)
  • Code (for example, for "mismatched types", it is E0308).
    It helps users get more information about the current error through an extended
    description of the problem in the error code index.
    Not all diagnostic have a code.
    For example, diagnostics created by lints don't have one.
  • Message.
    It is the main description of the problem.
    It should be general and able to stand on its own, so that it can make sense even in isolation.
  • Diagnostic window.
    This contains several things:
    • The path, line number and column of the beginning of the primary span.
    • The users' affected code and its surroundings.
    • Primary and secondary spans underlying the users' code.
      These spans can optionally contain one or more labels.
      • Primary spans should have enough text to describe the problem in such a
        way that if it were the only thing being displayed (for example,
        in an IDE) it would still make sense.
        Because it is "spatially aware" (it points at the code),
        it can generally be more succinct than the error message.
      • If cluttered output can be foreseen in cases when multiple span labels overlap,
        it is a good idea to tweak the output appropriately.
        For example, the if/else arms have incompatible types error uses different
        spans depending on whether the arms are all in the same line,
        if one of the arms is empty, and if none of those cases applies.
  • Sub-diagnostics.
    Any error can have multiple sub-diagnostics that look similar to the main part of the error.
    These are used for cases where the
    order of the explanation might not correspond with the order of the code.
    If the order of the explanation can be "order free", leveraging secondary labels
    in the main diagnostic is preferred, as it is typically less verbose.

The text should be matter of fact and avoid capitalization and periods,
unless multiple sentences are needed:

txt
error: the fobrulator needs to be krontrificated

When code or an identifier must appear in a message or label,
it should be surrounded with backticks:

txt
error: the identifier `foo.bar` is invalid

Error codes and explanations

Most errors have an associated error code.
Error codes are linked to long-form
explanations which contains an example of how to trigger the error and in-depth
details about the error.
They may be viewed with the --explain flag, or via the error index.

As a general rule, give an error a code (with an associated explanation) if the
explanation would give more information than the error itself.
A lot of the time it's better to put all the information in the emitted error itself.
However,
sometimes that would make the error verbose or there are too many possible
triggers to include useful information for all cases in the error,
in which case it's a good idea to add an explanation.[^estebank]
As always, if you are not sure, just ask your reviewer!

If you decide to add a new error with an associated error code,
please read this section for a guide and important details about the process.

[^estebank]: This rule of thumb was suggested by @estebank here.

Lints versus fixed diagnostics

Some messages are emitted via lints, where the user can control the level.
Most diagnostics are hard-coded such that the user cannot control the level.

Usually it is obvious whether a diagnostic should be "fixed" or a lint,
but there are some grey areas.

Here are a few examples:

  • Borrow checker errors: these are fixed errors.
    The user cannot adjust the level of these diagnostics to silence the borrow checker.
  • Dead code: this is a lint.
    While the user probably doesn't want dead code in their crate,
    making this a hard error would make refactoring and development very painful.
  • future-incompatible lints: these are silenceable lints.
    It was decided that making them fixed errors would cause too much breakage,
    so warnings are instead emitted,
    and will eventually be turned into fixed (hard) errors.

Hard-coded warnings (those using methods like span_warn) should be avoided for normal code,
preferring to use lints instead.
Some cases, such as warnings with CLI flags, will require the use of hard-coded warnings.

See the deny lint level below for guidelines when to
use an error-level lint instead of a fixed error.

Diagnostic output style guide

  • Write in plain simple English.
    If your message, when shown on a – possibly
    small – screen (which hasn't been cleaned for a while), cannot be understood
    by a normal programmer, who just came out of bed after a night partying,
    it's too complex.
  • Error, Warning, Note,
    and Help messages start with a lowercase letter and do not end with punctuation.
  • Error messages should be succinct.
    Users will see these error messages many times,
    and more verbose descriptions can be viewed with the --explain flag.
    That said, don't make it so terse that it's hard to understand.
  • The word "illegal" is illegal.
    Prefer "invalid" or a more specific word instead.
  • Errors should document the span of code where they occur (use
    rustc_errors::DiagCtxt's
    span_* methods or a diagnostic struct's #[primary_span] to easily do this).
    Also note other spans that have contributed to the error if the span isn't too large.
  • When emitting a message with span,
    try to reduce the span to the smallest amount possible that still signifies the issue
  • Try not to emit multiple error messages for the same error.
    This may require detecting duplicates.
  • When the compiler has too little information for a specific error message,
    consult with the compiler team to add new attributes for library code that
    allow adding more information.
    For example, see #[rustc_on_unimplemented].
    Use these annotations when available!
  • Keep in mind that Rust's learning curve is rather steep,
    and that the compiler messages are an important learning tool.
  • When talking about the compiler, call it the compiler, not Rust or rustc.
  • Use the Oxford comma when writing lists of items.
  • When mentioning attributes, use this form whenever possible: "the inline attribute".
    Don't include #[/#![/] delimiters or attribute arguments in the message or the span unless
    they are relevant.

Lint naming

From RFC 0344, lint names should be consistent, with the following guidelines:

The basic rule is: the lint name should make sense when read as "allow
lint-name" or "allow lint-name items".
For example, "allow deprecated items" and "allow dead_code" makes sense,
while "allow unsafe_block" is ungrammatical (should be plural).

  • Lint names should state the bad thing being checked for, e.g. deprecated,
    so that #[allow(deprecated)] (items) reads correctly.
    Thus, ctypes is not an appropriate name; improper_ctypes is.

  • Lints that apply to arbitrary items (like the stability lints) should just
    mention what they check for: use deprecated rather than deprecated_items.
    This keeps lint names short.
    (Again, think "allow lint-name items".)

  • If a lint applies to a specific grammatical class,
    mention that class and use the plural form: use unused_variables rather than unused_variable.
    This makes #[allow(unused_variables)] read correctly.

  • Lints that catch unnecessary, unused, or useless aspects of code should use
    the term unused, e.g. unused_imports, unused_typecasts.

  • Use snake case in the same way you would for function names.

Diagnostic levels

Guidelines for different diagnostic levels:

  • error: emitted when the compiler detects a problem that makes it unable to
    compile the program, either because the program is invalid or the programmer
    has decided to make a specific warning into an error.

  • warning: emitted when the compiler detects something odd about a program.
    Care should be taken when adding warnings to avoid warning fatigue,
    and avoid false-positives where there really isn't a problem with the code.
    Some examples of when it is appropriate to issue a warning:

    • A situation where the user should take action, such as swap out a deprecated item,
      or use a Result, but otherwise doesn't prevent compilation.
    • Unnecessary syntax that can be removed without affecting the semantics of the code.
      For example, unused code, or unnecessary unsafe.
    • Code that is very likely to be incorrect, dangerous, or confusing, but the
      language technically allows, and is not ready or confident enough to make an error.
      Examples are unused_comparisons (out of bounds comparisons) or
      bindings_with_variant_name (the user likely did not intend to create a
      binding in a pattern).
    • Future-incompatible lints, where something was
      accidentally or erroneously accepted in the past,
      but rejecting would cause excessive breakage in the ecosystem.
    • Stylistic choices.
      For example, camel or snake case, or the dyn trait warning in the 2018 edition.
      These have a high bar to be added, and should only be used in exceptional circumstances.
      Other stylistic choices should either be allow-by-default lints,
      or part of other tools like Clippy or rustfmt.
  • help: emitted following an error or warning to give additional
    information to the user about how to solve their problem.
    These messages often include a suggestion string and rustc_errors::Applicability
    confidence level to guide automated source fixes by tools.
    See the Suggestions section for more details.

    The error or warning portion should not suggest how to fix the problem,
    only the "help" sub-diagnostic should.

  • note: emitted to give more context and identify additional circumstances
    and parts of the code that caused the warning or error.
    For example, the borrow checker will note any previous conflicting borrows.

    help vs note: help should be used to show changes the user can
    possibly make to fix the problem.
    note should be used for everything else,
    such as other context, information and facts, online resources to read, etc.

Not to be confused with lint levels, whose guidelines are:

  • forbid: Lints should never default to forbid.

  • deny: Equivalent to error diagnostic level.
    Some examples:

    • A future-incompatible or edition-based lint that has graduated from the warning level.
    • Something that has an extremely high confidence that is incorrect,
      but still want an escape hatch to allow it to pass.
  • warn: Equivalent to the warning diagnostic level.
    See warning above for guidelines.

  • allow: Examples of the kinds of lints that should default to allow:

    • The lint has a too high false positive rate.
    • The lint is too opinionated.
    • The lint is experimental.
    • The lint is used for enforcing something that is not normally enforced.
      For example, the unsafe_code lint can be used to prevent usage of unsafe code.

More information about lint levels can be found in the rustc
book
and the reference.

Helpful tips and options

Finding the source of errors

There are three main ways to find where a given error is emitted:

  • grep for either a sub-part of the error message/label or error code.
    This usually works well and is straightforward, but there are some cases where
    the code emitting the error is removed from the code where the error is
    constructed behind a relatively deep call-stack.
    Even then, it is a good way to get your bearings.

  • Invoking rustc with the nightly-only flag -Z treat-err-as-bug=1
    will treat the first error being emitted as an Internal Compiler Error,
    which allows you to get a stack trace at the point the error has been emitted.
    Change the 1 to something else if you wish to trigger on a later error.

    There are limitations with this approach:

    • Some calls get elided from the stack trace because they get inlined in the compiled rustc.
    • The construction of the error is far away from where it is emitted,
      a problem similar to the one we faced with the grep approach.
      In some cases, we buffer multiple errors in order to emit them in order.
  • Invoking rustc with -Z track-diagnostics will print error creation
    locations alongside the error.

The regular development practices apply: judicious use of debug!() statements
and use of a debugger to trigger break points in order to figure out in what
order things are happening.

`Span`

Span is the primary data structure in rustc used to represent a
location in the code being compiled.
Spans are attached to most constructs in HIR and MIR,
allowing for more informative error reporting.

A Span can be looked up in a SourceMap to get a "snippet"
useful for displaying errors with span_to_snippet and other
similar methods on the SourceMap.

Error messages

The rustc_errors crate defines most of the utilities used for reporting errors.

Diagnostics can be implemented as types which implement the Diagnostic trait.
This is preferred for new diagnostics as it enforces a separation
between diagnostic emitting logic and the main code paths.
For less-complex diagnostics,
the Diagnostic trait can be derived -- see Diagnostic structs.
Within the trait implementation, the APIs described below can be used as normal.

DiagCtxt has methods that create and emit errors.
These methods
usually have names like span_err or struct_span_err or span_warn, etc...
There are lots of them; they emit different types of "errors", such as
warnings, errors, fatal errors, suggestions, etc.

In general, there are two classes of such methods: ones that emit an error
directly and ones that allow finer control over what to emit.
For example,
span_err emits the given error message at the given Span,
but struct_span_err instead returns a Diag.

Most of these methods will accept strings, but it is recommended that typed
identifiers for translatable diagnostics be used for new diagnostics (see Translation).

Diag allows you to add related notes and suggestions to an error
before emitting it by calling the emit method.
(Failing to either emit or cancel a Diag will result in an ICE.) See the
docs for more info on what you can do.

rust,ignore
// Get a `Diag`. This does _not_ emit an error yet.
let mut err = sess.dcx.struct_span_err(sp, fluent::example::example_error);

// In some cases, you might need to check if `sp` is generated by a macro to
// avoid printing weird errors about macro-generated code.

if let Ok(snippet) = sess.source_map().span_to_snippet(sp) {
    // Use the snippet to generate a suggested fix
    err.span_suggestion(suggestion_sp, fluent::example::try_qux_suggestion, format!("qux {}", snippet));
} else {
    // If we weren't able to generate a snippet, then emit a "help" message
    // instead of a concrete "suggestion". In practice this is unlikely to be
    // reached.
    err.span_help(suggestion_sp, fluent::example::qux_suggestion);
}

// emit the error
err.emit();
fluent
example-example-error = oh no! this is an error!
  .try-qux-suggestion = try using a qux here
  .qux-suggestion = you could use a qux here instead

Suggestions

In addition to telling the user exactly why their code is wrong,
it's oftentimes furthermore possible to tell them how to fix it.
To this end,
Diag offers a structured suggestions API, which formats code
suggestions pleasingly in the terminal, or (when the --error-format json flag
is passed) as JSON for consumption by tools like rustfix.

Not all suggestions should be applied mechanically;
they have a degree of confidence in the suggested code,
from high (Applicability::MachineApplicable) to low (Applicability::MaybeIncorrect).
Be conservative when choosing the level.
Use the span_suggestion method of Diag to make a suggestion.
The last argument provides a hint to tools whether the suggestion is mechanically applicable or not.

Suggestions point to one or more spans with corresponding code that will
replace their current content.

The message that accompanies them should be understandable in the following contexts:

  • shown as an independent sub-diagnostic (this is the default output)
  • shown as a label pointing at the affected span (this is done automatically if
    some heuristics for verbosity are met)
  • shown as a help sub-diagnostic with no content (used for cases where the
    suggestion is obvious from the text, but we still want to let tools to apply them)
  • not shown (used for very obvious cases, but we still want to allow tools to apply them)

For example, to make our qux suggestion machine-applicable, we would do:

rust,ignore
let mut err = sess.dcx.struct_span_err(sp, fluent::example::message);

if let Ok(snippet) = sess.source_map().span_to_snippet(sp) {
    err.span_suggestion(
        suggestion_sp,
        fluent::example::try_qux_suggestion,
        format!("qux {}", snippet),
        Applicability::MachineApplicable,
    );
} else {
    err.span_help(suggestion_sp, fluent::example::qux_suggestion);
}

err.emit();

This might emit an error like

console
$ rustc mycode.rs
error[E0999]: oh no! this is an error!
 --> mycode.rs:3:5
  |
3 |     sad()
  |     ^ help: try using a qux here: `qux sad()`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0999`.

In some cases, like when the suggestion spans multiple lines or when there are
multiple suggestions, the suggestions are displayed on their own:

console
error[E0999]: oh no! this is an error!
 --> mycode.rs:3:5
  |
3 |     sad()
  |     ^
help: try using a qux here:
  |
3 |     qux sad()
  |     ^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0999`.

The possible values of Applicability are:

  • MachineApplicable: Can be applied mechanically.
  • HasPlaceholders: Cannot be applied mechanically because it has placeholder
    text in the suggestions.
    For example: try adding a type: `let x: <type>` .
  • MaybeIncorrect: Cannot be applied mechanically because the suggestion may
    or may not be a good one.
  • Unspecified: Cannot be applied mechanically because we don't know which
    of the above cases it falls into.

Suggestion Style Guide

  • Suggestions should not be a question.
    In particular, language like "did you mean" should be avoided.
    Sometimes, it's unclear why a particular suggestion is being made.
    In these cases, it's better to be upfront about what the suggestion is.

    Compare "did you mean: Foo" vs.
    "there is a struct with a similar name: Foo".

  • The message should not contain any phrases like "the following", "as shown",
    etc. Use the span to convey what is being talked about.

  • The message may contain further instruction such as "to do xyz, use" or "to do xyz, use abc".

  • The message may contain a name of a function, variable, or type, but avoid whole expressions.

Lints

The compiler linting infrastructure is defined in the rustc_middle::lint module.

When do lints run?

Different lints will run at different times based on what information the lint needs to do its job.
Some lints get grouped into passes where the lints
within a pass are processed together via a single visitor.
Some of the passes are:

  • Pre-expansion pass: Works on AST nodes before macro expansion.
    This should generally be avoided.

    • Example: keyword_idents checks for identifiers that will become
      keywords in future editions, but is sensitive to identifiers used in macros.
  • Early lint pass: Works on AST nodes after macro expansion and name resolution,
    just before AST lowering.
    These lints are for purely syntactical lints.

    • Example: The unused_parens lint checks for parenthesized-expressions
      in situations where they are not needed, like an if condition.
  • Late lint pass: Works on HIR nodes, towards the end of analysis (after
    borrow checking, etc.). These lints have full type information available.
    Most lints are late.

    • Example: The invalid_value lint (which checks for obviously invalid
      uninitialized values) is a late lint because it needs type information to
      figure out whether a type allows being left uninitialized.
  • MIR pass: Works on MIR nodes.
    This isn't quite the same as other passes;
    lints that work on MIR nodes have their own methods for running.

    • Example: The arithmetic_overflow lint is emitted when it detects a
      constant value that may overflow.

Most lints work well via the pass systems,
and they have a fairly straightforward interface and easy way to integrate (mostly just implementing
a specific check function).
However,
some lints are easier to write when they live on a specific code path anywhere in the compiler.
For example, the unused_mut lint is implemented in the borrow checker as it requires some
information and state in the borrow checker.

Some of these inline lints fire before the linting system is ready.
Those lints will be buffered where they are held until later phases of the
compiler when the linting system is ready.
See Linting early in the compiler.

Lint definition terms

Lints are managed via the LintStore and get registered in various ways.
The following terms refer to the different classes of lints
generally based on how they are registered.

  • Built-in lints are defined inside the compiler source.
  • Driver-registered lints are registered when the compiler driver is created
    by an external driver.
    This is the mechanism used by Clippy, for example.
  • Tool lints are lints with a path prefix like clippy:: or rustdoc::.
  • Internal lints are the rustc:: scoped tool lints that only run on the
    rustc source tree itself and are defined in the compiler source like a regular built-in lint.

More information about lint registration can be found in the LintStore chapter.

Declaring a lint

The built-in compiler lints are defined in the rustc_lint crate.
Lints that need to be implemented in other crates are defined in rustc_lint_defs.
You should prefer to place lints in rustc_lint if possible.
One benefit is that it is close to the dependency root, so it can be much faster to work on.

Every lint is implemented via a struct that implements the LintPass trait
(you can also implement one of the more specific lint pass traits,
either EarlyLintPass or LateLintPass, depending on when is best for your lint to run).
The trait implementation allows you to check certain syntactic constructs
as the linter walks the AST.
You can then choose to emit lints in a very similar way to compile errors.

You also declare the metadata of a particular lint via the declare_lint! macro.
This macro includes the name, the default level, a short description, and some more details.

Note that the lint and the lint pass must be registered with the compiler.

For example, the following lint checks for uses
of while true { ... } and suggests using loop { ... } instead.

text
/* Detailed source-code truncated for AI context efficiency. */
fluent
example-use-loop = denote infinite loops with `loop {"{"} ... {"}"}`
  .suggestion = use `loop`

Edition-gated lints

Sometimes we want to change the behavior of a lint in a new edition.
To do this,
we just add the transition to our invocation of declare_lint!:

rust,ignore
declare_lint! {
    pub ANONYMOUS_PARAMETERS,
    Allow,
    "detects anonymous parameters",
    Edition::Edition2018 => Warn,
}

This makes the ANONYMOUS_PARAMETERS lint allow-by-default in the 2015 edition
but warn-by-default in the 2018 edition.

See Edition-specific lints for more information.

Feature-gated lints

Lints belonging to a feature should only be usable if the feature is enabled in the crate.
To support this, lint declarations can contain a feature gate like so:

rust,ignore
declare_lint! {
    pub SOME_LINT_NAME,
    Warn,
    "a new and useful, but feature gated lint",
    @feature_gate = sym::feature_name;
}

future-incompatible lints

The use of the term future-incompatible within the compiler has a slightly
broader meaning than what rustc exposes to users of the compiler.

Inside rustc,
future-incompatible lints are for signalling to the user their code may not compile in the future.
In general, future-incompatible code exists for two reasons:

  • The code is unsound, and the compiler mistakenly accepted it.
    While it is within Rust's backwards compatibility guarantees to fix the soundness hole
    (breaking the user's code), the lint is there to warn the user that this will happen
    in some upcoming version of rustc regardless of which edition the code uses.
    This is the meaning that rustc exclusively exposes to users as "future incompatible".
  • The code will either no longer compile or will change
    meaning in an upcoming edition.
    These are often called "edition lints" and can be
    typically seen in the various "edition compatibility" lint groups (e.g., rust_2021_compatibility)
    that are used to lint against code that will break if the user updates the crate's edition.
    See migration lints for more details.

A future-incompatible lint should be declared with the @future_incompatible additional "field":

rust,ignore
declare_lint! {
    pub ANONYMOUS_PARAMETERS,
    Allow,
    "detects anonymous parameters",
    @future_incompatible = FutureIncompatibleInfo {
        reason: fcw!(EditionError 2018 "slug-of-edition-guide-page")
    };
}

Notice the reason field which describes why the future incompatible change is happening.
This will change the diagnostic message the user receives as well as determine which
lint groups the lint is added to.
In the example above, the lint is an "edition lint" (since its "reason" is EditionError),
signifying to the user that the use of anonymous
parameters will no longer compile in Rust 2018 and beyond.

Inside LintStore::register_lints, lints with future_incompatible
fields get placed into either edition-based lint groups (if their reason is tied to
an edition) or into the future_incompatibility lint group.

If you need a combination of options that's not supported by the declare_lint! macro,
you can always change the declare_lint! macro to support this.

Renaming or removing a lint

If it is determined that a lint is either improperly named or no longer needed,
the lint must be registered for renaming or removal,
which will trigger a warning if a user tries to use the old lint name.
To declare a rename/remove,
add a line with store.register_renamed or store.register_removed to the code of the
rustc_lint::register_builtins function.

rust,ignore
store.register_renamed("single_use_lifetime", "single_use_lifetimes");

Lint groups

Lints can be turned on in groups.
These groups are declared in the
register_builtins function in rustc_lint::lib.
The add_lint_group! macro is used to declare a new group.

For example,

rust,ignore
add_lint_group!(sess,
    "nonstandard_style",
    NON_CAMEL_CASE_TYPES,
    NON_SNAKE_CASE,
    NON_UPPER_CASE_GLOBALS);

This defines the nonstandard_style group which turns on the listed lints.
A user can turn on these lints with a #![warn(nonstandard_style)] attribute in the source code,
or by passing -W nonstandard-style on the command line.

Some lint groups are created automatically in LintStore::register_lints.
For instance,
any lint declared with FutureIncompatibleInfo where the reason is
FutureIncompatibilityReason::FutureReleaseError (the default when
@future_incompatible is used in declare_lint!),
will be added to the future_incompatible lint group.
Editions also have their own lint groups
(e.g., rust_2021_compatibility) automatically generated for any lints signaling
future-incompatible code that will break in the specified edition.

Linting early in the compiler

On occasion, you may need to define a lint that runs before the linting system
has been initialized (e.g. during parsing or macro expansion). This is
problematic because we need to have computed lint levels to know whether we
should emit a warning or an error or nothing at all.

To solve this problem, we buffer the lints until the linting system is processed.
Session and ParseSess both have
buffer_lint methods that allow you to buffer a lint for later.
The linting system automatically takes care of handling buffered lints later.

Thus, to define a lint that runs early in the compilation,
one defines a lint like normal but invokes the lint with buffer_lint.

Linting even earlier in the compiler

The parser (rustc_ast) is interesting in that it cannot have dependencies on
any of the other rustc* crates.
In particular, it cannot depend on rustc_middle::lint or rustc_lint,
where all of the compiler linting infrastructure is defined.
That's troublesome!

To solve this, rustc_ast defines its own buffered lint type, which ParseSess::buffer_lint uses.
After macro expansion, these buffered lints are
then dumped into the Session::buffered_lints used by the rest of the compiler.

JSON diagnostic output

The compiler accepts an --error-format json flag to output
diagnostics as JSON objects (for the benefit of tools such as cargo fix).
It looks like this:

console
$ rustc json_error_demo.rs --error-format json
{"message":"cannot add `&str` to `{integer}`","code":{"code":"E0277","explanation":"\nYou tried to use a type which doesn't implement some trait in a place which\nexpected that trait. Erroneous code example:\n\n```compile_fail,E0277\n// here we declare the Foo trait with a bar method\ntrait Foo {\n    fn bar(&self);\n}\n\n// we now declare a function which takes an object implementing the Foo trait\nfn some_func<T: Foo>(foo: T) {\n    foo.bar();\n}\n\nfn main() {\n    // we now call the method with the i32 type, which doesn't implement\n    // the Foo trait\n    some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied\n}\n```\n\nIn order to fix this error, verify that the type you're using does implement\nthe trait. Example:\n\n```\ntrait Foo {\n    fn bar(&self);\n}\n\nfn some_func<T: Foo>(foo: T) {\n    foo.bar(); // we can now use this method since i32 implements the\n               // Foo trait\n}\n\n// we implement the trait on the i32 type\nimpl Foo for i32 {\n    fn bar(&self) {}\n}\n\nfn main() {\n    some_func(5i32); // ok!\n}\n```\n\nOr in a generic context, an erroneous code example would look like:\n\n```compile_fail,E0277\nfn some_func<T>(foo: T) {\n    println!(\"{:?}\", foo); // error: the trait `core::fmt::Debug` is not\n                           //        implemented for the type `T`\n}\n\nfn main() {\n    // We now call the method with the i32 type,\n    // which *does* implement the Debug trait.\n    some_func(5i32);\n}\n```\n\nNote that the error here is in the definition of the generic function: Although\nwe only call it with a parameter that does implement `Debug`, the compiler\nstill rejects the function: It must work with all possible input types. In\norder to make this example compile, we need to restrict the generic type we're\naccepting:\n\n```\nuse std::fmt;\n\n// Restrict the input type to types that implement Debug.\nfn some_func<T: fmt::Debug>(foo: T) {\n    println!(\"{:?}\", foo);\n}\n\nfn main() {\n    // Calling the method is still fine, as i32 implements Debug.\n    some_func(5i32);\n\n    // This would fail to compile now:\n    // struct WithoutDebug;\n    // some_func(WithoutDebug);\n}\n```\n\nRust only looks at the signature of the called function, as such it must\nalready specify all requirements that will be used for every type parameter.\n"},"level":"error","spans":[{"file_name":"json_error_demo.rs","byte_start":50,"byte_end":51,"line_start":4,"line_end":4,"column_start":7,"column_end":8,"is_primary":true,"text":[{"text":"    a + b","highlight_start":7,"highlight_end":8}],"label":"no implementation for `{integer} + &str`","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"the trait `std::ops::Add<&str>` is not implemented for `{integer}`","code":null,"level":"help","spans":[],"children":[],"rendered":null}],"rendered":"error[E0277]: cannot add `&str` to `{integer}`\n --> json_error_demo.rs:4:7\n  |\n4 |     a + b\n  |       ^ no implementation for `{integer} + &str`\n  |\n  = help: the trait `std::ops::Add<&str>` is not implemented for `{integer}`\n\n"}
{"message":"aborting due to previous error","code":null,"level":"error","spans":[],"children":[],"rendered":"error: aborting due to previous error\n\n"}
{"message":"For more information about this error, try `rustc --explain E0277`.","code":null,"level":"","spans":[],"children":[],"rendered":"For more information about this error, try `rustc --explain E0277`.\n"}

Note that the output is a series of lines, each of which is a JSON object,
but the series of lines taken together is, unfortunately, not
valid JSON, thwarting tools and tricks (such as piping to python3 -m json.tool) that require such.
(One speculates that this was intentional for LSP performance purposes,
so that each line/object can be sent as it is flushed?)

Also note the "rendered" field, which contains the "human" output as a
string; this was introduced so that UI tests could both make use of
the structured JSON and see the "human" output (well,
sans colors) without having to compile everything twice.

The "human" readable and the json format emitter can be found under rustc_errors,
both were moved from the rustc_ast crate to the
rustc_errors crate.

The JSON emitter defines its own Diagnostic
struct

(and sub-structs) for the JSON serialization.
Don't confuse this with
errors::Diag!

Diagnostic attributes

There are many attributes that can be used to generate or improve error messages:

  • diagnostic::on_unimplemented and rustc_on_unimplemented: customize unimplemented trait errors
  • diagnostic::on_move: customize borrowcheck errors
  • diagnostic::on_unknown: customize unresolved imports errors
  • diagnostic::on_unmatched_args: customize macro matcher errors
  • diagnostic::opaque: stop the internals of macros from showing up in error messages
  • diagnostic::on_const: customize non-const trait impl errors
  • rustc_as_ptr: used by the dangling_pointers_from_temporaries lint.
  • rustc_never_returns_null_ptr: used by the useless_ptr_null_checks lint.
  • rustc_no_implicit_autorefs: used for the dangerous_implicit_autorefs lint.

If possible, consider improving or implementing such an attribute rather than adding custom error
reporting code.


Src Doc Rustc Dev Guide Src Early Late Parameters (`src/doc/rustc-dev-guide/src/early-late-parameters.md`)

Early vs Late bound parameters

NOTE: This chapter largely talks about early/late bound as being solely relevant when discussing function item types/function definitions. This is potentially not completely true, async blocks and closures should likely be discussed somewhat in this chapter.

See also these blog posts from when the distinction between early and late bound parameters was
introduced: Intermingled parameter lists and Intermingled parameter lists, take 2.

What does it mean to be "early" bound or "late" bound

Every function definition has a corresponding ZST that implements the Fn* traits known as a function item type.
This part of the chapter will talk a little bit about the "desugaring" of function item types as it is useful context for explaining the difference between early bound and late bound generic parameters.

Let's start with a very trivial example involving no generic parameters:

rust
fn foo(a: String) -> u8 {
    # 1
    /* snip */
}

If we explicitly wrote out the definitions for the function item type corresponding to foo and its associated Fn impl it would look something like this:

rust,ignore
struct FooFnItem;

impl Fn<(String,)> for FooFnItem {
    type Output = u8;
    /* fn call(&self, ...) -> ... { ... } */
}

The builtin impls for the FnMut/FnOnce traits as well as the impls for Copy and Clone were omitted for brevity reasons (although these traits are implemented for function item types).

A slightly more complicated example would involve introducing generic parameters to the function:

rust
fn foo<T: Sized>(a: T) -> T {
    # a
    /* snip */
}

Writing out the definitions would look something like this:

rust,ignore
struct FooFnItem<T: Sized>(PhantomData<fn(T) -> T>);

impl<T: Sized> Fn<(T,)> for FooFnItem<T> {
    type Output = T;
    /* fn call(&self, ...) -> ... { ... } */
}

Note that the function item type FooFnItem is generic over some type parameter T as defined on the function foo.
However, not all generic parameters defined on functions are also defined on the function item type as demonstrated here:

rust
fn foo<'a, T: Sized>(a: &'a T) -> &'a T {
    # a
    /* snip */
}

With its "desugared" form looking like so:

rust,ignore
struct FooFnItem<T: Sized>(PhantomData<for<'a> fn(&'a T) -> &'a T>);

impl<'a, T: Sized> Fn<(&'a T,)> for FooFnItem<T> {
    type Output = &'a T;
    /* fn call(&self, ...) -> ... { ... } */
}

The lifetime parameter 'a from the function foo is not present on the function item type FooFnItem and is instead introduced on the builtin impl solely for use in representing the argument types.

Generic parameters not all being defined on the function item type means that there are two steps where generic arguments are provided when calling a function.

  1. Naming the function (e.g. let a = foo;) the arguments for FooFnItem are provided.
  2. Calling the function (e.g. a(&10);) any parameters defined on the builtin impl are provided.

This two-step system is where the early vs late naming scheme comes from, early bound parameters are provided in the earliest step (naming the function), whereas late bound parameters are provided in the latest step (calling the function).

Looking at the desugaring from the previous example we can tell that T is an early bound type parameter and 'a is a late bound lifetime parameter as T is present on the function item type but 'a is not.
See this example of calling foo annotated with where each generic parameter has an argument provided:

rust
fn foo<'a, T: Sized>(a: &'a T) -> &'a T {
    # a
    /* snip */
}

// Here we provide a type argument `String` to the
// type parameter `T` on the function item type
let my_func = foo::<String>;

// Here (implicitly) a lifetime argument is provided
// to the lifetime parameter `'a` on the builtin impl.
my_func(&String::new());

Differences between early and late bound parameters

Higher ranked function pointers and trait bounds

A generic parameter being late bound allows for more flexible usage of the function item.
For example, if we have some function foo with an early bound lifetime parameter and some function bar with a late bound lifetime parameter 'a, we would have the following builtin Fn impls:

rust,ignore
impl<'a> Fn<(&'a String,)> for FooFnItem<'a> { /* ... */ }
impl<'a> Fn<(&'a String,)> for BarFnItem { /* ... */ }

The bar function has a strictly more flexible signature as the function item type can be called with a borrow with any lifetime, whereas the foo function item type would only be callable with a borrow with the same lifetime on the function item type.
We can show this by simply trying to call foo's function item type multiple times with different lifetimes:

rust
// The `'a: 'a` bound forces this lifetime to be early bound.
fn foo<'a: 'a>(b: &'a String) -> &'a String { b }
fn bar<'a>(b: &'a String) -> &'a String { b }

// Early bound generic parameters are instantiated here when naming
// the function `foo`. As `'a` is early bound an argument is provided.
let f = foo::<'_>;

// Both function arguments are required to have the same lifetime as
// the lifetime parameter being early bound means that `f` is only
// callable for one specific lifetime.
//
// As we call this with borrows of different lifetimes, the borrow checker
// will error here.
f(&String::new());
f(&String::new());

The lifetime parameter on foo being early bound requires all callers of f to provide a borrow with the same lifetime.
In this example, we call foo's function item type twice, each time with a borrow of a temporary.
These two borrows could not possibly have lifetimes that overlap as the temporaries are only alive during the function call, not after, so we get a compilation error.

If the lifetime parameter on foo was late bound, this would be able to compile as each caller could provide a different lifetime argument for its borrow.
See the following example, which demonstrates this using the bar function defined above:

rust
# fn foo<'a: 'a>(b: &'a String) -> &'a String { b }
# fn bar<'a>(b: &'a String) -> &'a String { b }
#
// Early bound parameters are instantiated here, however as `'a` is
// late bound it is not provided here.
let b = bar;

// Late bound parameters are instantiated separately at each call site
// allowing different lifetimes to be used by each caller.
b(&String::new());
b(&String::new());

This is reflected in the ability to coerce function item types to higher ranked function pointers and prove higher ranked Fn trait bounds.
We can demonstrate this with the following example:

rust
// The `'a: 'a` bound forces this lifetime to be early bound.
fn foo<'a: 'a>(b: &'a String) -> &'a String { b }
fn bar<'a>(b: &'a String) -> &'a String { b }

fn accepts_hr_fn(_: impl for<'a> Fn(&'a String) -> &'a String) {}

fn higher_ranked_trait_bound() {
    let bar_fn_item = bar;
    accepts_hr_fn(bar_fn_item);

    let foo_fn_item = foo::<'_>;
    // errors
    accepts_hr_fn(foo_fn_item);
}

fn higher_ranked_fn_ptr() {
    let bar_fn_item = bar;
    let fn_ptr: for<'a> fn(&'a String) -> &'a String = bar_fn_item;

    let foo_fn_item = foo::<'_>;
    // errors
    let fn_ptr: for<'a> fn(&'a String) -> &'a String = foo_fn_item;
}

In both of these cases, the borrow checker errors as it does not consider foo_fn_item to be callable with a borrow of any lifetime.
This is due to the fact that the lifetime parameter on foo is early bound, causing foo_fn_item to have a type of FooFnItem<'_> which (as demonstrated by the desugared Fn impl) is only callable with a borrow of the same lifetime '_.

Turbofishing in the presence of late bound parameters

As mentioned previously, the distinction between early and late bound parameters means that there are two places where generic parameters are instantiated:

  • When naming a function (early)
  • When calling a function (late)

There is currently no syntax for explicitly specifying generic arguments for late bound parameters during the call step; generic arguments can only be specified for early bound parameters when naming a function.
The syntax foo::<'static>();, despite being part of a function call, behaves as (foo::<'static>)(); and instantiates the early bound generic parameters on the function item type.

See the following example:

rust
fn foo<'a>(b: &'a u32) -> &'a u32 { b }

let f /* : FooFnItem<????> */ = foo::<'static>;

The above example errors as the lifetime parameter 'a is late bound and so cannot be instantiated as part of the "naming a function" step.
If we make the lifetime parameter early bound we will see this code start to compile:

rust
fn foo<'a: 'a>(b: &'a u32) -> &'a u32 { b }

let f /* : FooFnItem<'static> */ = foo::<'static>;

What the current implementation of the compiler aims to do is error when specifying lifetime arguments to a function that has both early and late bound lifetime parameters.
In practice, due to excessive breakage, some cases are actually only future compatibility warnings (#42868):

  • When the amount of lifetime arguments is the same as the number of early bound lifetime parameters, a FCW is emitted instead of an error
  • An error is always downgraded to a FCW when using method call syntax

To demonstrate this we can write out the different kinds of functions and give them both a late and early bound lifetime:

rust,ignore
fn free_function<'a: 'a, 'b>(_: &'a (), _: &'b ()) {}

struct Foo;

trait Trait: Sized {
    fn trait_method<'a: 'a, 'b>(self, _: &'a (), _: &'b ());
    fn trait_function<'a: 'a, 'b>(_: &'a (), _: &'b ());
}

impl Trait for Foo {
    fn trait_method<'a: 'a, 'b>(self, _: &'a (), _: &'b ()) {}
    fn trait_function<'a: 'a, 'b>(_: &'a (), _: &'b ()) {}
}

impl Foo {
    fn inherent_method<'a: 'a, 'b>(self, _: &'a (), _: &'b ()) {}
    fn inherent_function<'a: 'a, 'b>(_: &'a (), _: &'b ()) {}
}

Then, for the first case, we can call each function with a single lifetime argument (corresponding to the one early bound lifetime parameter) and note that it only results in a FCW rather than a hard error.

rust
#![deny(late_bound_lifetime_arguments)]

# fn free_function<'a: 'a, 'b>(_: &'a (), _: &'b ()) {}
#
# struct Foo;
#
# trait Trait: Sized {
#     fn trait_method<'a: 'a, 'b>(self, _: &'a (), _: &'b ());
#     fn trait_function<'a: 'a, 'b>(_: &'a (), _: &'b ());
# }
#
# impl Trait for Foo {
#     fn trait_method<'a: 'a, 'b>(self, _: &'a (), _: &'b ()) {}
#     fn trait_function<'a: 'a, 'b>(_: &'a (), _: &'b ()) {}
# }
#
# impl Foo {
#     fn inherent_method<'a: 'a, 'b>(self, _: &'a (), _: &'b ()) {}
#     fn inherent_function<'a: 'a, 'b>(_: &'a (), _: &'b ()) {}
# }
#
// Specifying as many arguments as there are early
// bound parameters is always a future compat warning
Foo.trait_method::<'static>(&(), &());
Foo::trait_method::<'static>(Foo, &(), &());
Foo::trait_function::<'static>(&(), &());
Foo.inherent_method::<'static>(&(), &());
Foo::inherent_function::<'static>(&(), &());
free_function::<'static>(&(), &());

For the second case we call each function with more lifetime arguments than there are lifetime parameters (be it early or late bound) and note that method calls result in a FCW as opposed to the free/associated functions which result in a hard error:

rust
# fn free_function<'a: 'a, 'b>(_: &'a (), _: &'b ()) {}
#
# struct Foo;
#
# trait Trait: Sized {
#     fn trait_method<'a: 'a, 'b>(self, _: &'a (), _: &'b ());
#     fn trait_function<'a: 'a, 'b>(_: &'a (), _: &'b ());
# }
#
# impl Trait for Foo {
#     fn trait_method<'a: 'a, 'b>(self, _: &'a (), _: &'b ()) {}
#     fn trait_function<'a: 'a, 'b>(_: &'a (), _: &'b ()) {}
# }
#
# impl Foo {
#     fn inherent_method<'a: 'a, 'b>(self, _: &'a (), _: &'b ()) {}
#     fn inherent_function<'a: 'a, 'b>(_: &'a (), _: &'b ()) {}
# }
#
// Specifying more arguments than there are early
// bound parameters is a future compat warning when
// using method call syntax.
Foo.trait_method::<'static, 'static, 'static>(&(), &());
Foo.inherent_method::<'static, 'static, 'static>(&(), &());
// However, it is a hard error when not using method call syntax.
Foo::trait_method::<'static, 'static, 'static>(Foo, &(), &());
Foo::trait_function::<'static, 'static, 'static>(&(), &());
Foo::inherent_function::<'static, 'static, 'static>(&(), &());
free_function::<'static, 'static, 'static>(&(), &());

Even when specifying enough lifetime arguments for both the late and early bound lifetime parameter, these arguments are not actually used to annotate the lifetime provided to late bound parameters.
We can demonstrate this by turbofishing 'static to a function while providing a non-static borrow:

rust
struct Foo;

impl Foo {
    fn inherent_method<'a: 'a, 'b>(self, _: &'a (), _: &'b String ) {}
}

Foo.inherent_method::<'static, 'static>(&(), &String::new());

This compiles even though the &String::new() function argument does not have a 'static lifetime, this is because "extra" lifetime arguments are discarded rather than taken into account for late bound parameters when actually calling the function.

Liveness of types with late bound parameters

When checking type outlives bounds involving function item types we take into account early bound parameters.
For example:

rust
fn foo<T>(_: T) {}

fn requires_static<T: 'static>(_: T) {}

fn bar<T>() {
    let f /* : FooFnItem<T> */ = foo::<T>;
    requires_static(f);
}

As the type parameter T is early bound, the desugaring of the function item type for foo would look something like struct FooFnItem<T>.
Then, in order for FooFnItem<T>: 'static to hold, we must also require T: 'static to hold as otherwise we would wind up with soundness bugs.

Unfortunately, due to bugs in the compiler, we do not take into account early bound lifetimes, which is the cause of the open soundness bug #84366.
This means that it's impossible to demonstrate a "difference" between early/late bound parameters for liveness/type outlives bounds as the only kind of generic parameters that are able to be late bound are lifetimes which are handled incorrectly.

Regardless, in theory the code example below should demonstrate such a difference once #84366 is fixed:

rust
fn early_bound<'a: 'a>(_: &'a String) {}
fn late_bound<'a>(_: &'a String) {}

fn requires_static<T: 'static>(_: T) {}

fn bar<'b>() {
    let e = early_bound::<'b>;
    // this *should* error but does not
    requires_static(e);

    let l = late_bound;
    // this correctly does not error
    requires_static(l);
}

Requirements for a parameter to be late bound

Must be a lifetime parameter

Type and Const parameters are not able to be late bound as we do not have a way to support types such as dyn for<T> Fn(Box<T>) or for<T> fn(Box<T>).
Calling such types requires being able to monomorphize the underlying function which is not possible with indirection through dynamic dispatch.

Must not be used in a where clause

Currently when a generic parameter is used in a where clause it must be early bound.
For example:

rust
# trait Trait<'a> {}
fn foo<'a, T: Trait<'a>>(_: &'a String, _: T) {}

In this example the lifetime parameter 'a is considered to be early bound as it appears in the where clause T: Trait<'a>.
This is true even for "trivial" where clauses such as 'a: 'a or those implied by wellformedness of function arguments, for example:

rust
fn foo<'a: 'a>(_: &'a String) {}
fn bar<'a, T: 'a>(_: &'a T) {}

In both of these functions the lifetime parameter 'a would be considered to be early bound even though the where clauses they are used in arguably do not actually impose any constraints on the caller.

The reason for this restriction is a combination of two things:

  • We cannot prove bounds on late bound parameters until they have been instantiated
  • Function pointers and trait objects do not have a way to represent yet to be proven where clauses from the underlying function

Take the following example:

rust
trait Trait<'a> {}
fn foo<'a, T: Trait<'a>>(_: &'a T) {}

let f = foo::<String>;
let f = f as for<'a> fn(&'a String);
f(&String::new());

At some point during type checking an error should be emitted for this code as String does not implement Trait for any lifetime.

If the lifetime 'a were late bound then this becomes difficult to check.
When naming foo we do not know what lifetime should be used as part of the T: Trait<'a> trait bound as it has not yet been instantiated.
When coercing the function item type to a function pointer we have no way of tracking the String: Trait<'a> trait bound that must be proven when calling the function.

If the lifetime 'a is early bound (which it is in the current implementation in rustc), then the trait bound can be checked when naming the function foo.
Requiring parameters used in where clauses to be early bound gives a natural place to check where clauses defined on the function.

Finally, we do not require lifetimes to be early bound if they are used in implied bounds, for example:

rust
fn foo<'a, T>(_: &'a T) {}

let f = foo;
f(&String::new());
f(&String::new());

This code compiles, demonstrating that the lifetime parameter is late bound, even though 'a is used in the type &'a T which implicitly requires T: 'a to hold.
Implied bounds can be treated specially as any types introducing implied bounds are in the signature of the function pointer type, which means that when calling the function we know to prove T: 'a.

Must be constrained by argument types

It is important that builtin impls on function item types do not wind up with unconstrained generic parameters as this can lead to unsoundness.
This is the same kind of restriction as applies to user written impls, for example the following code results in an error:

rust
trait Trait {
    type Assoc;
}

impl<'a> Trait for u8 {
    type Assoc = &'a String;
}

The analogous example for builtin impls on function items would be the following:

rust,ignore
fn foo<'a>() -> &'a String { /* ... */ }

If the lifetime parameter 'a were to be late bound we would wind up with a builtin impl with an unconstrained lifetime, we can manually write out the desugaring for the function item type and its impls with 'a being late bound to demonstrate this:

rust,ignore
// NOTE: this is just for demonstration, in practice `'a` is early bound
struct FooFnItem;

impl<'a> Fn<()> for FooFnItem {
    type Output = &'a String;
    /* fn call(...) -> ... { ... } */
}

In order to avoid such a situation we consider 'a to be early bound which causes the lifetime on the impl to be constrained by the self type:

rust,ignore
struct FooFnItem<'a>(PhantomData<fn() -> &'a String>);

impl<'a> Fn<()> for FooFnItem<'a> {
    type Output = &'a String;
    /* fn call(...) -> ... { ... } */
}

Src Doc Rustc Dev Guide Src Effects (`src/doc/rustc-dev-guide/src/effects.md`)

Effects, const traits, and const condition checking

The `HostEffect` predicate

HostEffectPredicates are a kind of predicate from [const] Tr or const Tr bounds.
It has a trait reference,
and a constness which could be Maybe or Const, depending on the bound.
Because [const] Tr, or rather Maybe bounds
apply differently based on whichever contexts they are in,
they have different behavior than normal bounds.
Where normal trait bounds on a function such as
T: Tr are collected within the clauses_of query to be proven when a
function is called and to be assumed within the function,
bounds such as T: [const] Tr will behave as a normal trait bound and add T: Tr to the result
from clauses_of, but also adds a HostEffectPredicate to the const_conditions query.

On the other hand, T: const Tr bounds do not change meaning across contexts,
therefore they will result in HostEffect(T: Tr, const) being added to clauses_of,
and not const_conditions.

The `const_conditions` query

clauses_of represents a set of clauses that need to be proven to use an item.
For example, to use foo in the example below:

rust
fn foo<T>() where T: Default {}

We must be able to prove that T implements Default.
In a similar vein,
const_conditions represents a set of predicates that need to be proven to use
an item in const contexts.
If we adjust the example above to use const trait bounds:

rust
const fn foo<T>() where T: [const] Default {}

Then foo would get a HostEffect(T: Default, maybe) in the const_conditions query,
suggesting that in order to call foo from const contexts, one must
prove that T has a const implementation of Default.

Enforcement of `const_conditions`

const_conditions are currently checked in various places.

Every call in HIR from a const context (which includes const fn and const
items) will check that const_conditions of the function we are calling hold.
This is done in FnCtxt::enforce_context_effects.
Note that we don't check if the function is only referred to but not called,
as the following code needs to compile:

rust
const fn hi<T: [const] Default>() -> T {
    T::default()
}
const X: fn() -> u32 = hi::<u32>;

For a trait impl to be well-formed,
we must be able to prove the const_conditions of the trait from the impl's environment.
This is checked in wfcheck::check_impl.

Here's an example:

rust
const trait Bar {}
const trait Foo: [const] Bar {}
// `const_conditions` contains `HostEffect(Self: Bar, maybe)`

impl const Bar for () {}
impl const Foo for () {}
// ^ here we check `const_conditions` for the impl to be well-formed

Methods of trait impls must not have stricter bounds than the method of the
trait that they are implementing.
To check that the methods are compatible,
a hybrid environment is constructed with the predicates of the impl plus the
predicates of the trait method, and we attempt to prove the predicates of the impl method.
We do the same for const_conditions:

rust
const trait Foo {
    fn hi<T: [const] Default>();
}

impl<T: [const] Clone> Foo for Vec<T> {
    fn hi<T: [const] PartialEq>();
    // ^ we can't prove `T: [const] PartialEq` given `T: [const] Clone` and
    // `T: [const] Default`, therefore we know that the method on the impl
    // is stricter than the method on the trait.
}

These checks are done in compare_method_clause_entailment.
A similar function that does the same check for associated types is called
compare_type_clause_entailment.
Both of these need to consider const_conditions when in const contexts.

In MIR, as part of const checking, const_conditions of items that are called
are revalidated again in Checker::revalidate_conditional_constness.

`explicit_implied_const_bounds` on associated types and traits

Bounds on associated types, opaque types,
and supertraits such as the following have their bounds represented differently:

rust
trait Foo: [const] PartialEq {
    type X: [const] PartialEq;
}

fn foo() -> impl [const] PartialEq {
    // ^ unimplemented syntax
}

Unlike const_conditions, which need to be proved for callers,
and can be assumed inside the definition (e.g. trait
bounds on functions), these bounds need to be proved at definition (at the impl,
or when returning the opaque) but can be assumed for callers.
The non-const equivalent of these bounds are called explicit_item_bounds.

These bounds are checked in compare_impl_item::check_type_bounds for HIR typeck,
evaluate_host_effect_from_item_bounds in the old solver and
consider_additional_alias_assumptions in the new solver.

Proving `HostEffectPredicate`s

HostEffectPredicates are implemented both in the old solver and the new trait solver.
In general, we can prove a HostEffect predicate when either of these conditions are met:

  • The predicate can be assumed from caller bounds;
  • The type has a const impl for the trait, and that const conditions on the impl holds,
    and that the explicit_implied_const_bounds on the trait holds; or
  • The type has a built-in implementation for the trait in const contexts.
    For example, Fn may be implemented by function items if their const conditions are satisfied,
    or Destruct is implemented in const contexts if the type can
    be dropped at compile time.

More on const traits

To be expanded later.

The `#[rustc_non_const_trait_method]` attribute

This is intended for internal (standard library) usage only.
With this attribute applied to a trait method,
the compiler will not check the default body of this method for ability to run in compile time.
Users of the trait will also not be allowed to use this trait method in const contexts.
This attribute is primarily
used for constifying large traits such as Iterator without having to make all
its methods const at the same time.

This attribute should not be present while stabilizing the trait as const.


Src Doc Rustc Dev Guide Src External Repos (`src/doc/rustc-dev-guide/src/external-repos.md`)

Using external repositories

The rust-lang/rust git repository depends on several other repos in the rust-lang organization.
There are three main ways we use dependencies:

  1. As a Cargo dependency through crates.io (e.g. rustc-rayon)
  2. As a git (e.g. clippy) or a josh (e.g. miri) subtree
  3. As a git submodule (e.g. cargo)

As a general rule:

  • Use crates.io for libraries that could be useful for others in the ecosystem
  • Use subtrees for tools that depend on compiler internals and need to be updated if there are breaking
    changes
  • Use submodules for tools that are independent of the compiler

External dependencies (subtrees)

The following external projects are managed using some form of a subtree:

In contrast to submodule dependencies (see below for those),
the subtree dependencies are just regular files and directories which can be updated in-tree.
However, if possible, enhancements, bug fixes, etc. specific
to these tools should be filed against the tools directly in their respective upstream repositories.
The exception is that when rustc changes are required to implement a new tool feature or test,
that should happen in one collective rustc PR.

Similarly, if your rustc changes break the build of some subtree dependency,
you should include fixes for those in the rustc PR as well.

subtree dependencies are currently managed by two distinct approaches:

  • Using git subtree
  • Using the josh tool
    • miri
    • rust-analyzer
    • rustc_codegen_cranelift
    • rustc-dev-guide
    • rustfmt
    • compiler-builtins
    • stdarch

Josh subtrees

The josh tool is an alternative to git subtrees, which manages git history in a different way and scales better to larger repositories.
Specific tooling is required to work with josh.
We provide a helper tool, rustc-josh-sync, to help with the synchronization, described below.

Synchronizing a Josh subtree

We use a dedicated tool called rustc-josh-sync for performing Josh subtree updates.
The commands below can be used for all our Josh subtrees, although note that miri
requires you to perform some additional steps during pulls.

You can install the tool using the following command:

text
cargo install --locked --git https://github.com/rust-lang/josh-sync

Both pulls (synchronize changes from rust-lang/rust into the subtree) and pushes (synchronize
changes from the subtree to rust-lang/rust) are performed from the subtree repository (so first
switch to its repository checkout directory in your terminal).

Performing pull

  1. Checkout a new branch that will be used to create a PR into the subtree
  2. Run the pull command
    text
    rustc-josh-sync pull
  3. Push the branch to your fork and create a PR into the subtree repository
    • If you have gh CLI installed, rustc-josh-sync can create the PR for you.

Performing push

NOTE:
Before you proceed, look at some guidance related to Git on josh-sync README.

  1. Run the push command to create a branch named `` in a rustc fork under the <gh-username> account
    text
    rustc-josh-sync push <branch-name> <gh-username>
  2. Create a PR from `` into rust-lang/rust

Creating a new Josh subtree dependency

If you want to migrate a repository dependency from git subtree or git submodule to josh, you can check out this guide.

Synchronizing a git subtree

Periodically the changes made to subtree based dependencies need to be synchronized between this
repository and the upstream tool repositories.

Subtree synchronizations are typically handled by the respective tool maintainers.
Other users are welcome to submit synchronization PRs.
However, in order to do so,
you will need to modify your local git installation and follow a very precise set of instructions.
These instructions are documented, along with several useful tips and tricks,
in the syncing subtree changes section in Clippy's Contributing guide.
The instructions are applicable for use with any subtree-based tool;
just be sure to use the correct corresponding subtree directory and remote repository.

The synchronization process goes in two directions: subtree push and subtree pull.

A subtree push takes all the changes that happened to the copy in this repo and creates commits
on the remote repo that match the local changes.
Every local commit that touched the subtree causes a commit on the remote repo,
but is modified to move the files from the specified directory to the tool repo root.

A subtree pull takes all changes since the last subtree pull
from the tool repo and adds these commits to the rustc repo along with a merge commit that moves
the tool changes into the specified directory in the Rust repository.

It is recommended that you always do a push first and get that merged to the default branch of the tool.
Then, when you do a pull, the merge works without conflicts.
While it's definitely possible to resolve conflicts during a pull, you may have to redo the conflict
resolution if your PR doesn't get merged fast enough and there are new conflicts.
Do not try to
rebase the result of a git subtree pull; rebasing merge commits is a bad idea in general.

You always need to specify the -P prefix to the subtree directory and the corresponding remote
repository.
If you specify the wrong directory or repository
you'll get very fun merges that try to push the wrong directory to the wrong remote repository.
Luckily you can just abort this without any consequences by throwing away either the pulled commits
in rustc or the pushed branch on the remote and try again.
It is usually fairly obvious
that this is happening because you suddenly get thousands of commits that want to be synchronized.

Creating a new subtree dependency

If you want to create a new subtree dependency from an existing repository,
call (from this repository's root directory!)

text
git subtree add -P src/tools/clippy https://github.com/rust-lang/rust-clippy.git master

This will create a new commit, which you may not rebase under any circumstances!
Delete the commit and redo the operation if you need to rebase.

Now you're done;
the src/tools/clippy directory behaves as if Clippy were part of the rustc monorepo,
so no one but you (or others that synchronize subtrees) actually needs to use git subtree.

External dependencies (submodules)

Building Rust will also use external git repositories tracked using git submodules.
The complete list may be found in the .gitmodules file.
Some of these projects are required (like stdarch for the standard library) and
some of them are optional (like src/doc/book).

Usage of submodules is discussed more in the Using Git chapter.

Some of the submodules are allowed to be in a "broken" state where they
either don't build or their tests don't pass, e.g. the documentation books
like The Rust Reference.
Maintainers of these projects will be notified when the project is in a broken state,
and they should fix them as soon as possible.
The current status is tracked on the toolstate website.
More information may be found on the Forge Toolstate chapter.
In practice, it is very rare for documentation to have broken toolstate.

Breakage is not allowed in the beta and stable channels,
and must be addressed before the PR is merged.
They are also not allowed to be broken on main in the week leading up to the beta cut.


Src Doc Rustc Dev Guide Src Feature Gate Check (`src/doc/rustc-dev-guide/src/feature-gate-check.md`)

Feature Gate Checking

For the how-to steps to add, remove, rename, or stabilize feature gates,
see Feature gates.

Feature gates prevent usage of unstable language and library features without a
nightly-only #![feature(...)] opt-in.
This chapter documents the implementation
of feature gating: where gates are defined, how they are enabled, and how usage is verified.

Feature Definitions

All feature gate definitions are located in the rustc_feature crate:

The rustc_feature::Features type represents the active feature set for a crate.
Helpers like enabled, incomplete, and internal are used during compilation to check status.

Collecting Features

Before AST validation or expansion, rustc collects crate-level
#![feature(...)] attributes to build the active Features set.

  • The collection happens in rustc_expand/src/config.rs in features.
  • Each #![feature] entry is classified against the unstable, accepted, and removed tables:
    • Removed features cause an immediate error.
    • Accepted features are recorded but do not require nightly.
      On stable/beta, maybe_stage_features in
      rustc_ast_passes/src/feature_gate.rs emits the non-nightly
      diagnostic and lists stable features, which is where the "already
      stabilized" messaging comes from.
    • Unstable features are recorded as enabled.
    • Unknown features are treated as library features and validated later.
  • With -Z allow-features=..., any unstable or unknown feature
    not in the allowlist is rejected.
  • RUSTC_BOOTSTRAP feeds into UnstableFeatures::from_environment.
    This variable controls whether the compiler is treated as "nightly", allowing
    feature gates to be bypassed during bootstrapping or explicitly disabled (-1).

Parser Gating

Some syntax is detected and gated during parsing.
The parser records spans for
later checking to keep diagnostics consistent and deferred until after parsing.

Checking Pass

The central logic lives in rustc_ast_passes/src/feature_gate.rs, primarily
in check_crate and its AST visitor.

`check_crate`

check_crate performs high-level validation:

  • maybe_stage_features: Rejects #![feature] on stable/beta.
  • check_incompatible_features: Ensures incompatible feature combinations
    (declared in rustc_feature::INCOMPATIBLE_FEATURES) are not used together.
  • check_new_solver_banned_features: Bans features incompatible with
    compiler mode for the next trait solver.
  • check_features_requiring_new_solver: Requires the new trait solver for
    features incompatible with the old solver.
  • Parser-gated spans: Processes the GatedSpans recorded during parsing
    (see Checking GatedSpans).

Checking `GatedSpans`

check_crate iterates over sess.psess.gated_spans:

  • The gate_all! macro emits diagnostics for each gated span if the feature is not enabled.
  • Some gates have extra logic (e.g., yield can be allowed by coroutines or
    gen_blocks).
  • Legacy gates (e.g., box_patterns, try_blocks) may use a separate path that
    emits future-incompatibility warnings instead of hard errors.

AST Visitor

A PostExpansionVisitor walks the expanded AST to check constructs that are
easier to validate after expansion.

  • The visitor uses helper macros (gate!, gate_alt!, gate_multi!) to check:
    1. Is the feature enabled?
    2. Does span.allows_unstable permit it (for internal compiler macros)?
  • Examples include trait_alias, decl_macro, extern types, and various impl Trait forms.

Attributes and `cfg`

Beyond syntax, rustc also gates attributes and cfg options.

Built-in attributes

  • rustc_ast_passes::check_attribute inspects attributes against BUILTIN_ATTRIBUTE_SET.
  • If the attribute is AttributeGate::Gated and the feature isn’t enabled,
    feature_err is emitted.

`cfg` options

Diagnostics

Diagnostic helpers are located in rustc_session/src/parse.rs.

  • feature_err and feature_warn emit standardized diagnostics, attaching the
    tracking issue number where possible.
  • Span::allows_unstable in rustc_span/src/lib.rs checks if a span originates
    from a macro marked with #[allow_internal_unstable].
    This allows internal
    macros to use unstable features on stable channels while enforcing gates for user code.

Src Doc Rustc Dev Guide Src Feature Gates (`src/doc/rustc-dev-guide/src/feature-gates.md`)

Feature gates

This chapter is intended to provide basic help for adding, removing, and modifying feature gates.

For how rustc enforces and checks feature gates in the compiler pipeline,
see Feature Gate Checking.

Note that this is specific to language feature gates; library feature gates use a different
mechanism
.

Adding a feature gate

See "Stability in code" in the "Implementing new features" section for instructions.

Removing a feature gate

To remove a feature gate, follow these steps:

  1. Remove the feature gate declaration in rustc_feature/src/unstable.rs.
    It will look like this:

    rust,ignore
    /// description of feature
    (unstable, $feature_name, "$version", Some($tracking_issue_number))
  2. Add a modified version of the feature gate declaration that you just
    removed to rustc_feature/src/removed.rs:

    rust,ignore
    /// description of feature
    (removed, $old_feature_name, "$version", Some($tracking_issue_number),
     Some("$why_it_was_removed"))

Renaming a feature gate

To rename a feature gate, follow these steps (the first two are the same steps
to follow when removing a feature gate):

  1. Remove the old feature gate declaration in rustc_feature/src/unstable.rs.
    It will look like this:

    rust,ignore
    /// description of feature
    (unstable, $old_feature_name, "$version", Some($tracking_issue_number))
  2. Add a modified version of the old feature gate declaration that you just
    removed to rustc_feature/src/removed.rs:

    rust,ignore
    /// description of feature
    /// Renamed to `$new_feature_name`
    (removed, $old_feature_name, "$version", Some($tracking_issue_number),
     Some("renamed to `$new_feature_name`"))
  3. Add a feature gate declaration with the new name to rustc_feature/src/unstable.rs.
    It should look very similar to the old declaration:

    rust,ignore
    /// description of feature
    (unstable, $new_feature_name, "$version", Some($tracking_issue_number))

Stabilizing a feature

See "Updating the feature-gate listing" in the "Stabilizing Features" chapter for instructions.
There are additional steps you will need to take beyond just updating the declaration!


Src Doc Rustc Dev Guide Src Fuzzing (`src/doc/rustc-dev-guide/src/fuzzing.md`)

Fuzzing

For the purposes of this guide, fuzzing is any testing methodology that
involves compiling a wide variety of programs in an attempt to uncover bugs in rustc.
Fuzzing is often used to find internal compiler errors (ICEs).
Fuzzing can be beneficial, because it can find bugs before users run into them.
It also provides small, self-contained programs that make the bug easier to track down.
However, some common mistakes can reduce the helpfulness of fuzzing and end up
making contributors' lives harder.
To maximize your positive impact on the Rust
project, please read this guide before reporting fuzzer-generated bugs!

Guidelines

In a nutshell

Please do:

  • Ensure the bug is still present on the latest nightly rustc
  • Include a reasonably minimal, standalone example along with any bug report
  • Include all of the information requested in the bug report template
  • Search for existing reports with the same message and query stack
  • Format the test case with rustfmt
  • Indicate that the bug was found by fuzzing

Please don't:

  • Don't report lots of bugs that use internal features, including but not
    limited to custom_mir, lang_items, no_core, and rustc_attrs.
  • Don't seed your fuzzer with inputs that are known to crash rustc (details below).

Discussion

If you're not sure whether or not an ICE is a duplicate of one that's already
been reported, please go ahead and report it and link to issues you think might be related.
In general, ICEs on the same line but with different query stacks are usually distinct bugs.
For example, #109020 and #109129 had similar error messages:

text
error: internal compiler error: compiler/rustc_middle/src/ty/normalize_erasing_regions.rs:195:90: Failed to normalize <[closure@src/main.rs:36:25: 36:28] as std::ops::FnOnce<(Emplacable<()>,)>>::Output, maybe try to call `try_normalize_erasing_regions` instead
text
error: internal compiler error: compiler/rustc_middle/src/ty/normalize_erasing_regions.rs:195:90: Failed to normalize <() as Project>::Assoc, maybe try to call `try_normalize_erasing_regions` instead

However, they have different query stacks:

text
query stack during panic:
#0 [fn_abi_of_instance] computing call ABI of `<[closure@src/main.rs:36:25: 36:28] as core::ops::function::FnOnce<(Emplacable<()>,)>>::call_once - shim(vtable)`
end of query stack
text
query stack during panic:
#0 [check_mod_attrs] checking attributes in top-level module
#1 [analysis] running analysis passes on this crate
end of query stack

Building a corpus

When building a corpus, be sure to avoid collecting tests that are already known to crash rustc.
A fuzzer that is seeded with such tests is more likely to
generate bugs with the same root cause.
The simplest way to avoid this is to loop over each file in the corpus, see if it causes an
ICE, and remove it if so.

To build a corpus, you may want to use:

  • The rustc/rust-analyzer/clippy test suites (or even source code) --- though avoid
    tests that are already known to cause failures, which often begin with comments
    like //@ failure-status: 101 or //@ known-bug: #NNN.
  • The already-fixed ICEs in the archived Glacier repository --- though
    avoid the unfixed ones in ices/!

Extra credit

Here are a few things you can do to help the Rust project after filing an ICE:

  • Bisect the bug to figure out when it was introduced.
    If you find the regressing PR / commit, you can mark the issue with the label S-has-bisection.
    If not, consider applying E-needs-bisection instead.
  • Fix "distractions": problems with the test case that don't contribute to
    triggering the ICE, such as syntax errors or borrow-checking errors
  • Minimize the test case (see below).
    If successful, you can label the issue with S-has-mcve.
    Otherwise, you can apply E-needs-mcve.
  • Add the minimal test case to the rust-lang/rust repo as a crash test.
    While you're at it, consider including other "untracked" crashes in your PR.
    Please don't forget to mark all relevant issues with S-bug-has-test once your PR is merged.

See also applying and removing labels.

Minimization

It is helpful to carefully minimize the fuzzer-generated input.
When minimizing, be careful to preserve the original error, and avoid introducing
distracting problems such as syntax, type-checking, or borrow-checking errors.

There are some tools that can help with minimization.
If you're not sure how to avoid introducing syntax, type-, and borrow-checking errors while using
these tools, post both the complete and minimized test cases.
Generally,
syntax-aware tools give the best results in the least amount of time.
treereduce-rust and picireny are syntax-aware.
halfempty is not, but is generally a high-quality tool.

Effective fuzzing

When fuzzing rustc, you may want to avoid generating machine code, since this
is mostly done by LLVM.
Try --emit=mir instead.

A variety of compiler flags can uncover different issues.
-Zmir-opt-level=4 will turn on MIR optimization passes that are not run by default, potentially
uncovering interesting bugs.
-Zvalidate-mir can help uncover such bugs.

If you're fuzzing a compiler you built, you may want to build it with -C target-cpu=native or even PGO/BOLT to squeeze out a few more executions per second.
Of course, it's best to try multiple build configurations and see
what actually results in superior throughput.

You may want to build rustc from source with debug assertions to find
additional bugs, though this can slow down fuzzing by
requiring extra work for every execution.
To enable debug assertions, add this to bootstrap.toml when compiling rustc:

toml
rust.debug-assertions = true

ICEs that require debug assertions to reproduce should be tagged
requires-debug-assertions.

Existing projects

  • fuzz-rustc demonstrates how to fuzz rustc with libfuzzer
  • icemaker runs rustc and other tools on a large number of source
    files with a variety of flags to catch ICEs
  • tree-splicer generates new source files by combining existing
    ones while maintaining correct syntax

Src Doc Rustc Dev Guide Src Generic Parameters Summary (`src/doc/rustc-dev-guide/src/generic-parameters-summary.md`)

Generic parameter definitions

This chapter will discuss how rustc tracks what generic parameters are introduced.
For example, given some struct Foo<T>,
how does rustc track that Foo defines some type parameter T (and no other generic parameters).

This will not cover how we track generic parameters introduced via for<'a> syntax (e.g. in where clauses or fn types), which is covered elsewhere in the chapter on Binders .

`ty::Generics`

The generic parameters introduced by an item are tracked by the ty::Generics struct.
Sometimes items allow usage of generics defined on parent items,
and this is accomplished via the ty::Generics struct having an optional field to specify a parent item to inherit generic parameters of.
For example, given the following code:

rust,ignore
trait Trait<T> {
    fn foo<U>(&self);
}

The ty::Generics used for foo would contain [U] and a parent of Some(Trait).
Trait would have a ty::Generics containing [Self, T] with a parent of None.

The GenericParamDef struct is used to represent each individual generic parameter in a ty::Generics listing. The GenericParamDef struct contains information about the generic parameter, for example its name, defid, what kind of parameter it is (i.e. type, const, lifetime).

GenericParamDef also contains a u32 index representing what position the parameter is (starting from the outermost parent), this is the value used to represent usages of generic parameters (more on this in the chapter on representing types).

Interestingly, ty::Generics does not currently contain every generic parameter defined on an item.
In the case of functions, it only contains the early bound parameters.


Src Doc Rustc Dev Guide Src Git (`src/doc/rustc-dev-guide/src/git.md`)

Using Git

The Rust project uses Git to manage its source code.
In order to contribute, you'll need some familiarity with its features so that your changes
can be incorporated into the compiler.

The goal of this page is to cover some of the more common questions and
problems new contributors face.
Although some Git basics will be covered here,
if you find that this is still a little too fast for you, it might make sense
to first read some introductions to Git, such as the Beginner and Getting
started sections of this tutorial from Atlassian.
GitHub also provides documentation and guides for beginners, or you can consult the
more in depth book from Git.

This guide is incomplete.
If you run into trouble with git that this page doesn't help with,
please open an issue so we can document how to fix it.

Prerequisites

We'll assume that you've installed Git, forked rust-lang/rust, and cloned the
forked repo to your PC.
We'll use the command line interface to interact
with Git; there are also a number of GUIs and IDE integrations that can
generally do the same things.

If you've cloned your fork, then you will be able to reference it with origin in your local repo.
It may be helpful to also set up a remote for the official rust-lang/rust repo via

console
git remote add upstream https://github.com/rust-lang/rust.git

if you're using HTTPS, or

console
git remote add upstream [email protected]:rust-lang/rust.git

if you're using SSH.

NOTE: This page is dedicated to workflows for rust-lang/rust, but will likely be
useful when contributing to other repositories in the Rust project.

Standard Process

Below is the normal procedure that you're likely to use for most minor changes and PRs:

  1. Ensure that you're making your changes on top of main: git checkout main.
  2. Get the latest changes from the Rust repo: git pull upstream main --ff-only.
    (see No-Merge Policy for more info about this).
  3. Make a new branch for your change: git checkout -b issue-12345-fix.
  4. Make some changes to the repo and test them.
  5. Stage your changes via git add src/changed/file.rs src/another/change.rs
    and then commit them with git commit.
    Of course, making intermediate commits may be a good idea as well.
    Avoid git add ., as it makes it too easy to
    unintentionally commit changes that should not be committed, such as submodule updates.
    You can use git status to check if there are any files you forgot to stage.
  6. Push your changes to your fork: git push --set-upstream origin issue-12345-fix
    (After adding commits, you can use git push and after rebasing or
    pulling-and-rebasing, you can use git push --force-with-lease).
  7. Open a PR from your fork to rust-lang/rust's main branch.

If you end up needing to rebase and are hitting conflicts, see Rebasing.
If you want to track upstream while working on long-running feature/issue, see
Keeping things up to date.

If your reviewer requests changes, the procedure for those changes looks much
the same, with some steps skipped:

  1. Ensure that you're making changes to the most recent version of your code:
    git checkout issue-12345-fix.
  2. Make, stage, and commit your additional changes just like before.
  3. Push those changes to your fork: git push.

Troubleshooting git issues

You don't need to clone rust-lang/rust from scratch if it's out of date!
Even if you think you've messed it up beyond repair, there are ways to fix
the git state that don't require downloading the whole repository again.
Here are some common issues you might run into:

I made a merge commit by accident.

Git has two ways to update your branch with the newest changes: merging and rebasing.
Rust uses rebasing.
If you make a merge commit, it's not too hard to fix: git rebase -i upstream/main.

See Rebasing for more about rebasing.

I deleted my fork on GitHub!

This is not a problem from git's perspective.
If you run git remote -v,
it will say something like this:

console
$ git remote -v
origin  [email protected]:jyn514/rust.git (fetch)
origin  [email protected]:jyn514/rust.git (push)
upstream        https://github.com/rust-lang/rust (fetch)
upstream        https://github.com/rust-lang/rust (fetch)

If you renamed your fork, you can change the URL like this:

console
git remote set-url origin <URL>

where the `` is your new fork.

I changed a submodule by accident

Usually people notice this when rustbot posts a comment on github that cargo has been modified:

You might also notice conflicts in the web UI:

The most common cause is that you rebased after a change and ran git add . without first running
x to update the submodules.
Alternatively, you might have run cargo fmt instead of x fmt
and modified files in a submodule, then committed the changes.

To fix it, do the following things (if you changed a submodule other than cargo,
replace src/tools/cargo with the path to that submodule):

  1. See which commit has the accidental changes: git log --stat -n1 src/tools/cargo
  2. Revert the changes to that commit: git checkout <my-commit>~ src/tools/cargo.
    Type ~ literally but replace <my-commit> with the output from step 1.
  3. Tell git to commit the changes: git commit --fixup <my-commit>
  4. Repeat steps 1-3 for all the submodules you modified.
    • If you modified the submodule in several different commits, you will need to repeat steps 1-3
      for each commit you modified.
      You'll know when to stop when the git log command shows a commit
      that's not authored by you.
  5. Squash your changes into the existing commits: git rebase --autosquash -i upstream/main
  6. Push your changes.

I see "error: cannot rebase" when I try to rebase

These are two common errors to see when rebasing:

console
error: cannot rebase: Your index contains uncommitted changes.
error: Please commit or stash them.
console
error: cannot rebase: You have unstaged changes.
error: Please commit or stash them.

(See https://git-scm.com/book/en/v2/Getting-Started-What-is-Git%3F#_the_three_states for the difference between the two.)

This means you have made changes since the last time you made a commit.
To be able to rebase, either
commit your changes, or make a temporary commit called a "stash" to have them still not be committed
when you finish rebasing.
You may want to configure git to make this "stash" automatically, which
will prevent the "cannot rebase" error in nearly all cases:

console
git config --global rebase.autostash true

See <https://git-scm.com/book/en/v2/Git-Tools-Stashi