Open-Source Wikis

/

Rust

/

Compiler

/

Diagnostics and errors

rust-lang/rust

Diagnostics and errors

Compiler error messages are one of Rust's most-loved features. The infrastructure that produces them spans several crates.

Pieces

Crate Role
rustc_errors Diagnostic core: Diag, DiagCtxt, severity, emitting, suggestion machinery
rustc_error_messages Loading + interpolating Fluent message catalogs
rustc_error_codes Long-form EXXXX explanations
rustc_span Span (source-location) and source-map types

Anatomy of a diagnostic

A user-facing error message has up to four parts:

error[E0308]: mismatched types
  --> src/main.rs:3:18
   |
 3 |     let x: u32 = "hello";
   |            ---   ^^^^^^^ expected `u32`, found `&str`
   |            |
   |            expected due to this
   |
   = note: `&str` is not `u32`
help: replace the value with a numeric literal
   |
 3 |     let x: u32 = 0;
   |                  ~
  • The header — code (E0308), severity (error), message
  • The primary span — main location with carets
  • Labels — secondary annotations on other spans
  • Notes / helps — additional context
  • Suggestions — concrete code-replacement edits

rustc_errors::Diag is a builder for all of this. Most diagnostics are emitted via the high-level derive macros described below.

The Diagnostic derive

The convention is to define each error as a struct and derive Diagnostic:

#[derive(Diagnostic)]
#[diag(borrowck_borrow_through_x, code = E0501)]
struct BorrowThroughX {
    #[primary_span]
    span: Span,
    name: Symbol,
    #[label]
    other_span: Span,
    #[help]
    help_span: Span,
}

The #[diag(...)] attribute references a Fluent message ID; the field attributes describe which spans get labels, helps, suggestions, etc. The compiler proc-macro in rustc_macros generates the right Diagnostic impl.

For sub-diagnostics (notes/helps that may or may not be attached), #[derive(Subdiagnostic)] does the same dance.

For lints, #[derive(LintDiagnostic)].

Fluent message catalogs

User-facing strings live in messages.ftl files in each crate using Fluent, a localization-friendly format:

borrowck_borrow_through_x =
    cannot borrow `{$name}` through `*{$name}`
    .label = borrow occurs here
    .help = consider doing X instead

Each crate has its own messages file (compiler/rustc_X/messages.ftl); messages are namespaced by the crate name (borrowck_borrow_through_x). At runtime, the messages are loaded by rustc_error_messages and interpolated with the diagnostic's struct fields.

This setup lets the project, in principle, ship localized diagnostics. In practice English is the only translation (with some experimental ones), but the infrastructure is there.

Error codes (EXXXX)

Some diagnostics have a code like E0308. The long-form explanation lives in rustc_error_codes/src/error_codes/EXXXX.md and is shown when the user runs rustc --explain E0308. Long explanations are also rendered into the Rust Error Index book.

Not all errors have codes — only those stable enough to be promised long-form. New codes are minted carefully; team policy is to prefer linking to a long-form entry over a fresh code.

Severity levels

Level Compilation continues? Rendered as
error only briefly (errors are fatal at end of phase) red
warning yes yellow
note yes blue, attached to another diagnostic
help yes green, attached
failure_note yes only at end-of-compile if errors
fatal no — aborts immediately red
bug / delayed_bug depends — these are ICE indicators crash with "thread 'rustc' panicked"

delayed_bug is interesting: it records "an invariant violation that must have produced a real error somewhere" and crashes only if no real error was produced by end of compilation. This is how we maintain "if rustc emits an error, it crashes only when intended" guarantees.

ErrorGuaranteed

ErrorGuaranteed is a zero-sized witness that an error has been emitted. Many APIs require it as proof:

fn analyze(...) -> Result<Output, ErrorGuaranteed> { ... }

You get an ErrorGuaranteed only by calling dcx().emit_err(...) (or related methods). This makes "did we emit an error before this assert?" machine-checked: you can't construct an ErrorGuaranteed out of thin air, so the type system enforces that you've reported something.

Suggestions

A suggestion is a candidate code edit. They have:

  • A span to replace
  • The replacement text
  • An applicabilityMachineApplicable, MaybeIncorrect, HasPlaceholders, Unspecified

MachineApplicable is the gold standard — it means the IDE / cargo fix --automatic can apply the edit safely without user review. MaybeIncorrect is the most common; the suggestion may need human judgment.

Span machinery

rustc_span is the lowest layer. A Span is essentially (BytePos, BytePos, SyntaxContext) — start, end, hygiene context. The SourceMap resolves spans back to source files and line/column numbers.

Two concepts that show up a lot:

  • SyntaxContext — encodes which macro expansion an identifier came from. Used by hygiene checking.
  • Symbol — interned &str. == is O(1) because it's pointer equality. Symbol::intern("foo") interns and returns a stable handle.

Pretty-printing

The diagnostic emitter (HumanEmitter, JSONEmitter) lives in rustc_errors::emitter. It handles:

  • Color and Unicode (or ASCII fallback)
  • Window-size-aware truncation of long lines
  • Snippet rendering with carets and underlines
  • Sorting / merging multiple diagnostics on the same span

JSON output (--error-format=json) emits one JSON object per diagnostic, used by IDEs.

Lint diagnostics

Lints have their own pathway because they need to know the lint level (allow/warn/deny/forbid) before deciding to emit. The LintDiagnostic derive and tcx.lint(...) API integrate with the lint level table.

Entry points for modification

  • A new diagnostic for a compiler crate → add a struct with #[derive(Diagnostic)] in that crate's errors.rs, an entry in its messages.ftl, then call dcx.emit_err(...)
  • A new error code → add EXXXX.md under rustc_error_codes/src/error_codes/, register in error_codes.rs
  • Better suggestion engine → look at Suggestions in rustc_errors
  • Improving an existing diagnostic — find the messages.ftl line, find where the matching struct is emitted, and tweak

See also

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Diagnostics and errors – Rust wiki | Factory