How to Contribute to the Rust Compiler (rustc)

Start by finding a good first issue on the Rust triage repository, then follow the official contribution guide to set up your build environment and run the test suite.

The compiler is just code

You hit a compiler error that makes no sense. The message mentions a lifetime you didn't write or a trait bound that seems impossible. You have a fix in mind. You open the Rust repository and see a directory structure that looks like a maze. You assume contributing to the compiler requires a PhD in type theory and intimate knowledge of LLVM internals.

That assumption is wrong. The compiler is a collection of Rust crates. It uses standard Rust patterns, albeit with heavy use of macros and unsafe blocks that are carefully documented. You can fix the error message. You can improve the diagnostic. You can add a feature. The barrier is not the code complexity. The barrier is the build system and the workflow. Once you understand x.py and the bootstrap process, you can contribute.

How the build system works

The Rust compiler builds itself. This process is called bootstrapping. You start with a pre-compiled compiler binary provided by the repository. This is stage 0. Stage 0 compiles the source code to produce a new compiler binary. This is stage 1. Stage 1 is built entirely from the code you checked out. You can go one step further. Stage 1 compiles the source again to produce stage 2. If stage 1 and stage 2 produce identical binaries, the compiler is stable and self-hosting.

For daily development, you only need stage 1. Stage 2 is reserved for final verification before a release or when you are changing the compiler's ability to compile itself. Building stage 2 takes significantly longer and is unnecessary for most contributions.

The build driver is a Python script named x.py. It manages submodules, configures the build, runs tests, and formats code. You do not need rustup to build the compiler. The repository contains the toolchain. x.py handles everything.

git clone https://github.com/rust-lang/rust.git
cd rust
# x.py is the entry point for all build and test operations.
# It reads config.toml for build options.
./x.py --help

Convention aside: Always copy config.toml.example to config.toml before your first build. The example file contains comments explaining every option. Edit config.toml to disable features you do not need. Building the full distribution with all tools takes hours. Building just the compiler takes minutes. The community expects contributors to use config.toml to keep local builds fast.

Minimal build and test

Clone the repository and run a stage 1 build. The first build downloads dependencies and compiles the standard library. It takes time. Subsequent builds are incremental.

# Clone the repository and enter the directory.
git clone https://github.com/rust-lang/rust.git
cd rust

# Run a stage 1 build.
# This produces a compiler from the source code using the pre-built stage 0.
./x.py build --stage 1

Once the build succeeds, run the test suite. The test suite is massive. You rarely need to run the full suite. Run tests for the component you are changing.

# Run tests for a specific component.
# The parser tests live in src/test/ui/parser.
./x.py test src/test/ui/parser

# Run a single test file by path.
./x.py test src/test/ui/parser/your-test.rs

The test output shows passed and failed tests. If a test fails, x.py prints the diff between the expected output and the actual output. This diff tells you exactly what changed.

Directory structure and where code lives

The repository is organized into four main directories. Knowing where to look saves hours of searching.

src/compiler contains the compiler crates. rustc_driver is the entry point. rustc_hir handles the High-Level Intermediate Representation. rustc_middle contains the core data structures and analysis passes. rustc_errors manages diagnostics and error reporting. If you are fixing an error message, start in rustc_errors or the specific pass that generates the error.

src/library contains the standard library. core is the dependency-free core. alloc provides heap allocation. std is the full standard library. If you are changing Vec or Option, you are working here.

src/tools contains auxiliary tools. clippy, rustfmt, rustdoc, and cargo live here. These tools are built alongside the compiler but are separate projects.

src/test contains the test suite. ui tests check the output of the compiler, including error messages. run-pass tests check that code compiles and runs. compile-fail tests check that code is rejected. If you are changing behavior, you need to add or update tests here.

Realistic contribution workflow

Find an issue tagged with E-easy or E-mentor. These issues are curated for new contributors. E-easy means the fix is small and well-defined. E-mentor means a team member is available to guide you. Fork the repository, create a branch, and make your changes.

# Create a branch for your fix.
git checkout -b fix-issue-12345

# Edit the source code.
# For example, modify an error message in src/compiler/rustc_errors/src/lib.rs.
# Add a test case in src/test/ui/your-test.rs.

# Run the specific test to verify your change.
./x.py test src/test/ui/your-test.rs

If you change an error message, the test will fail because the expected output no longer matches. The fix is to bless the test. Blessing updates the expected output files with the actual output.

# Bless the test to update expected output.
# This overwrites the .stderr or .stdout files with the new output.
./x.py test --bless src/test/ui/your-test.rs

# Commit the code changes and the blessed test files.
git add src/compiler/rustc_errors/src/lib.rs
git add src/test/ui/your-test.rs
git add src/test/ui/your-test.stderr
git commit -m "Fix issue #12345: improve error message for missing trait"

Convention aside: Always commit the blessed files. The CI will fail if the expected output does not match. Reviewers expect the blessed files to be part of the commit. Do not ask reviewers to bless the tests for you.

Before pushing, run the formatting and linting tools. The project enforces strict style guidelines. x.py integrates rustfmt and clippy.

# Format the code to match project style.
./x.py fmt

# Run clippy lints on the compiler code.
./x.py clippy --stage 1

Push the branch and open a Pull Request. The CI system runs extensive tests. Be prepared for code reviews. The compiler team is thorough. They will ask for clarifications, suggest improvements, and check for edge cases. If the CI fails, fix the errors locally and push again. The PR updates automatically.

When the review is complete, a reviewer will comment with @bors r+. This tells the merge bot to merge the PR. You do not need to do anything. The bot handles the merge.

Pitfalls and how to avoid them

The build fails with a missing crate error. This usually means config.toml is misconfigured or submodules are not initialized. Run ./x.py --help to check your configuration. Ensure submodules are updated.

The test suite takes too long. You are running the full suite. Use ./x.py test with a specific path. Run only the tests related to your change. Use --stage 1 to skip stage 2 verification.

The CI passes locally but fails remotely. This happens when the remote environment has different defaults. Check the CI logs. The logs show the exact command that failed. Reproduce the failure locally by running the same command.

You change code but forget to add a test. Every change needs a test. The compiler team rejects PRs without tests. Add a test in src/test/ui or the appropriate directory. The test proves the fix works and prevents regressions.

The compiler uses unsafe code. You see unsafe blocks in the source. Do not be intimidated. The unsafe blocks are accompanied by // SAFETY: comments that list the invariants. Read the comments. They explain why the code is safe.

// Example from rustc_data_structures
unsafe {
    // SAFETY:
    // 1. The pointer is derived from a valid allocation in the arena.
    // 2. The arena is immutable during this phase, preventing data races.
    // 3. The alignment matches the type being read.
    ptr::read_unaligned(ptr)
}

Treat the // SAFETY: comment as a proof. If you modify the code, update the comment to reflect the new invariants. If you cannot write the proof, do not touch the code.

Decision matrix

Use ./x.py build --stage 1 when you want a fast build for local development and testing. Use ./x.py build --stage 2 when you need to verify the compiler can bootstrap itself, typically before a major release or when changing the bootstrap process. Use ./x.py test --bless when you change error messages or diagnostics and need to update the expected output files. Use ./x.py fmt and ./x.py clippy when you are ready to submit a PR to ensure style compliance. Use @rustbot label +T-compiler when you need the compiler team to review your PR. Use @bors r+ when a reviewer has approved your changes and you are authorized to merge.

Trust the build system. If x.py says the build failed, check your configuration and the error message before assuming the code is broken. The compiler is strict for a reason.

Where to go next