Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sharing the trait solver with rust-analyzer

rust-analyzer can be viewed as a compiler frontend: it performs tasks similar to the parts of rustc that run before code generation, such as parsing, lexing, AST construction and lowering, HIR lowering, and even limited MIR building and const evaluation.

However, because rust-analyzer is primarily a language server, its architecture differs in several important ways from that of rustc. Despite these differences, a substantial portion of its responsibilities—most notably type inference and trait solving—overlap with the compiler.

To avoid duplication and to maintain consistency between the two implementations, rust-analyzer reuses several crates from rustc, relying on shared abstractions wherever possible.

Shared Crates

Currently, rust-analyzer depends on several rustc_* crates from the compiler:

  • rustc_abi
  • rustc_ast_ir
  • rustc_index
  • rustc_lexer
  • rustc_next_trait_solver
  • rustc_parse_format
  • rustc_pattern_analysis
  • rustc_type_ir

Since these crates are not published on crates.io as part of the compiler’s normal distribution process, rust-analyzer maintains its own publishing pipeline. It uses the rustc-auto-publish script to publish these crates to crates.io with the prefix ra-ap-rustc_* (for example: https://crates.io/crates/ra-ap-rustc_next_trait_solver). rust-analyzer then depends on these re-published crates in its own build.

For trait solving specifically, the primary shared crates are rustc_type_ir and rustc_next_trait_solver, which provide the core IR and solver logic used by both compiler frontends.

The Abstraction Layer

Because rust-analyzer is a language server, it must handle frequently changing source code and partially invalid or incomplete source codes. This requires an infrastructure quite different from rustc’s, especially in the layers between the source code and the HIR—for example, Ty and its backing interner.

To bridge these differences, the compiler provides rustc_type_ir as an abstraction layer shared between rustc and rust-analyzer. This crate defines the fundamental interfaces used to represent types, predicates, and the context required by the trait solver. Both rustc and rust-analyzer implement these traits for their own concrete type representations, and rustc_next_trait_solver is written to be generic over these abstractions.

In addition to these interfaces, rustc_type_ir also includes several non-trivial components built on top of the abstraction layer—such as elaboration logic and the search graph machinery used by the solver.

Design Concepts

rustc_next_trait_solver is intended to depend only on the abstract interfaces defined in rustc_type_ir. To support this, the type-system traits in rustc_type_ir must expose every interface the solver requires—for example, creating a new inference type variable (rustc, rust-analyzer). For items that do not need compiler-specific representations, rustc_type_ir defines them directly as structs or enums parameterized over these traits—for example, TraitRef.

The following are some notable items from the rustc_type_ir crate.

trait Interner

The central trait in this design is Interner, which specifies all implementation-specific details for both rustc and rust-analyzer. Among its essential responsibilities:

  • it specifies the concrete types used by the implementation via its associated types; these form the backbone of how each compiler frontend instantiates the shared IR,
  • it provides the context required by the solver (e.g., querying lang items, enumerating all blanket impls for a trait);
  • and it must implement IrPrint for formatting and tracing.
    In practice, these IrPrint impls simply route to existing formatting logic inside rustc or rust-analyzer.

In rustc, TyCtxt implements Interner: it exposes the rustc’s query methods, and the required Interner trait methods are implemented by invoking those queries. In rust-analyzer, the implementing type is named DbInterner (as it performs most interning through the salsa database), and most of its methods are backed by salsa queries rather than rustc queries.

mod inherent

Another notable item in rustc_type_ir is the inherent module. This module provides forward definitions of inherent methods—expressed as traits—corresponding to methods that exist on compiler-specific types such as Ty or GenericArg.
These definitions allow the generic crates (such as rustc_next_trait_solver) to call methods that are implemented differently in rustc and rust-analyzer.

Code in generic crates should import these definitions with:

#![allow(unused)]
fn main() {
use inherent::*;
}

These forward definitions must never be used inside the concrete implementations themselves. Crates that implement the traits from mod inherent should call the actual inherent methods on their concrete types once those types are nameable.

You can find rustc’s implementations of these traits in the rustc_middle::ty::inherent module. For rust-analyzer, the corresponding implementations are located across several modules under hir_ty::next_solver, such as hir_ty::next_solver::region.

trait InferCtxtLike and trait SolverDelegate

These two traits correspond to the role of InferCtxt in rustc.

InferCtxtLike must be defined in rustc_infer due to coherence constraints(orphan rules). As a result, it cannot provide functionality that lives in rustc_trait_selection. Instead, behavior that depends on trait-solving logic is abstracted into a separate trait, SolverDelegate. Its implementator in rustc is simply a newtype struct over InferCtxt in rustc_trait_selection.

(In rust-analyzer, it is also implemented for a newtype wrapper over its own InferCtxt, primarily to mirror rustc’s structure, although this is not strictly necessary because all solver-related logic already resides in the hir-ty crate.)

In the long term, the ideal design is to move all of the logic currently expressed through SolverDelegate into rustc_next_trait_solver, with any required core operations added directly to InferCtxtLike. This would allow more of the solver’s behavior to live entirely inside the shared solver crate.

rustc_type_ir::search_graph::{Cx, Delegate}

The abstraction traits Cx and Delegate are already implemented within rustc_next_trait_solver itself. Therefore, users of the shared crates—both rustc and rust-analyzer—do not need to provide their own implementations.

These traits exist primarily to support fuzzing of the search graph independently of the full trait solver. This infrastructure is used by the external fuzzing project: https://github.com/lcnr/search_graph_fuzz.

Long-term plans for supporting rust-analyzer

In general, we aim to support rust-analyzer just as well as rustc in these shared crates—provided doing so does not substantially harm rustc’s performance or maintainability. (e.g., #145377, #146111, #146182 and #147723)

Shared crates that require nightly-only features must guard such code behind a nightly feature flag, since rust-analyzer is built with the stable toolchain.

Looking forward, we plan to uplift more shared logic into rustc_type_ir. There are still duplicated implementations between rustc and rust-analyzer—such as ObligationCtxt (rustc, rust-analyzer) and type coercion logic (rustc, rust-analyzer)—that we would like to unify over time.