Compiler Diagnostics and Error Messages
rustc is renowned for producing clear, actionable compiler diagnostics. Rather than emitting cryptic one-line errors, every diagnostic pinpoints the exact source spans involved, explains why the code violates a language rule, and often provides copy-pasteable code suggestions (help:) that fix the issue automatically via cargo fix.
Anatomy of a rustc Diagnostic
Every compiler message follows a structured layout with six components:
error[E0384]: cannot assign twice to immutable variable `x` <-- 1. Level, Error Code & Headline
--> src/main.rs:4:5 <-- 2. File Path, Line & Column
|
2 | let x = 10;
| - <-- 3. Secondary Span (context/origin)
| |
| first assignment to `x`
| help: consider making this binding mutable: `mut x`<-- 4. Actionable Fix Suggestion
3 | println!("{x}");
4 | x = 20;
| ^^^^^^ cannot assign twice to immutable variable <-- 5. Primary Span (`^^^` error site)
|
= note: variables are immutable by default in Rust <-- 6. Additional Notes (`= note:`)
For more information about this error, try `rustc --explain E0384`.
| Diagnostic Element | Meaning |
|---|---|
Level (error vs warning) |
An error halts compilation and prevents producing a binary; a warning allows compilation to succeed while alerting you to suspicious or unused code |
Error Code ([E0xxx]) |
Unique identifier in the Rust Compiler Error Index (queryable offline with rustc --explain E0384) |
Primary Span (^^^) |
Marks the exact tokens where the violation occurred |
Secondary Span (---) |
Marks earlier declarations, moves, or borrows that explain why the primary span is invalid |
= note: |
Explains underlying type definitions, trait bounds, or active lint settings |
help: |
Concrete code diff (- / +) that resolves the error or warning |
Common Error Code Families to Recognize
Grouping rustc error codes by category helps you immediately recognize which compiler subsystem flagged your code:
| Error Code | Category | Typical Message & Cause |
|---|---|---|
E0308 |
Type Checking | mismatched types (for example, returning () instead of i32, or mixing i32 and u32) |
E0277 |
Trait Resolution | the trait bound 'T: Trait' is not satisfied (for example, printing with {} when Display is not implemented) |
E0282 / E0284 |
Type Inference | type annotations needed (for example, calling .parse() or .collect() without specifying the output type) |
E0425 / E0433 |
Name Resolution | cannot find value/type in this scope (typo in variable name, out-of-scope binding, or missing use import) |
E0381 |
Control Flow / Init | used binding isn't initialized (reading a variable before assigning a value on all branches) |
E0382 |
Ownership / Moves | borrow of moved value (using a non-Copy variable after transferring ownership elsewhere) |
E0384 / E0596 |
Mutability | cannot assign twice to immutable variable / cannot borrow as mutable (missing let mut) |
E0502 / E0499 |
Borrow Checker | cannot borrow as mutable because it is also borrowed as immutable (overlapping & and &mut borrows) |
E0515 / E0597 |
Lifetimes | cannot return reference to local variable / does not live long enough (dangling reference prevention) |
E0004 / E0005 |
Pattern Matching | non-exhaustive patterns in match (E0004) or refutable pattern in local binding in let (E0005) |
Controlling Lint Levels (allow, warn, deny, forbid)
While hard language errors (E0308, E0382, E0502) can never be silenced, compiler lints (such as unused_variables, dead_code, deprecated, or Clippy lints) can be configured at four severity levels using attributes (#[...] on an item, #![...] at the top of a crate) or [lints] in Cargo.toml:
| Lint Level | Attribute Syntax | Effect |
|---|---|---|
| Allow | #[allow(dead_code)] |
Silences the warning completely for the annotated item or module |
| Expect | #[expect(unused_variables)] |
Silences the warning, but emits a warning if the suppressed lint no longer triggers! (Prefer over #[allow]!) |
| Warn | #[warn(missing_docs)] |
Emits a compiler warning when violated |
| Deny | #[deny(unsafe_code)] / RUSTFLAGS="-D warnings" |
Turns the lint into a hard compile error (can still be overridden locally with #[allow]) |
| Forbid | #![forbid(unsafe_code)] |
Turns the lint into a hard compile error that cannot be overridden anywhere inside the crate |
#[allow(dead_code)]
fn reserved_for_future_use() -> i32 {
42
}
fn main() {
#[expect(unused_variables)]
let placeholder = 100;
println!("Compiled with zero warnings!");
}
Compiled with zero warnings!
Automatically Applying Compiler Suggestions (cargo fix)
Many rustc and cargo clippy warnings include machine-applicable suggestions (such as removing unused imports, prefixing unused variables with _, or adding mut). Running cargo fix applies these fixes directly to your source files:
# Automatically apply compiler suggestions across the workspace
cargo fix --allow-dirty
# Automatically apply Clippy lint suggestions
cargo clippy --fix --allow-dirty