Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Rust (2024 edition)  Module 1 – Getting started: rustup, Cargo and compiler messages

Reading rustc errors and using --explain

Read every part of a rustc error, look up its code with rustc --explain, fix errors in the order the compiler finds them, and choose lint levels.

  • Beginner
  • 15 minutes
  • Examples run with Rust 1.99.0 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Read the parts of a rustc diagnostic: level, code, primary label, notes and help
  • Find an error code's explanation with rustc --explain and the error index
  • Fix errors in the order rustc reports them, using cargo check for fast feedback
  • Change a warning's level with an attribute, the [lints] table or Cargo's build.warnings

Before you start

What you’ll learn · Before you start

What you will learn

  • Read the parts of a rustc diagnostic: level, code, primary label, notes and help
  • Find an error code's explanation with rustc --explain and the error index
  • Fix errors in the order rustc reports them, using cargo check for fast feedback
  • Change a warning's level with an attribute, the [lints] table or Cargo's build.warnings

Before you start

On this page

A six-line program that uses a list after handing it to another variable gets an error of 18 lines, and each part of it means something: what went wrong, where, how it came about, and one way to fix it. Rust’s compiler errors are long because they explain, and learning to read them pays off every day you write Rust.

This lesson takes one error apart, looks its code up with rustc --explain, and follows a package through three rounds of cargo check to show why fixing one error can uncover another. Then it turns to warnings: each one names a lint: A named check that the compiler or Clippy runs on code that compiles, such as unused_variables. Its level, allow, warn, deny or forbid, decides whether it is silent, a warning or an error., and you decide whether that lint warns, fails the build or stays quiet.

Every rustc error has the same parts

Here is the six-line program and the error the compiler printed for it:

roster.rs: a list used after it was moved Rust · roster.rs
fn main() {
    let names = vec![String::from("Asha"), String::from("Ravi")];
    let roster = names;
    println!("{} names", names.len());
    println!("{roster:?}");
}

Output (exit status 1)

Printed as an error (standard error)

error[E0382]: borrow of moved value: `names`
 --> roster.rs:4:26
  |
2 |     let names = vec![String::from("Asha"), String::from("Ravi")];
  |         ----- move occurs because `names` has type `Vec<String>`, which does not implement the `Copy` trait
3 |     let roster = names;
  |                  ----- value moved here
4 |     println!("{} names", names.len());
  |                          ^^^^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let roster = names.clone();
  |                       ++++++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0382`.

Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O roster.rs && ./roster

That is a diagnostic: A message from the compiler about your code: an error, which stops the build, or a warning, which does not. It gives a level, often a code such as E0382, the place, labels on the source and, when it can, help., and every one has the same parts in the same places. Step through them on this error:

Interactive · step by step The parts of one rustc error Step through the error rustc printed for roster.rs above. Each step puts one part in focus and says what it tells you.
The parts of one rustc error, step 8 of 8. Showing error[E0382], borrow of moved value, --> roster.rs:4:26, ^^^^^ on line 4, ----- on lines 2 and 3, help: names.clone(), The last lines.error[E0382]level and codeborrow of moved valuewhat went wrong--> roster.rs:4:26file, line, column^^^^^ on line 4the use it refuses----- on lines 2 and 3how it came abouthelp: names.clone()a suggested editThe last linesthe count and --explain

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. Level and code. error means rustc built nothing; warning would mean it built the code anyway. E0382 is the code, the key to look the error up by.
  2. The message. borrow of moved value: names says what went wrong, and names the value it is about.
  3. Where. The --> line gives the place the error is about: file roster.rs, line 4, column 26. Editors and terminals can jump straight there.
  4. The primary label. Under the code, ^^^^^ marks the exact place the message is about, with a label: value borrowed here after move. This is the line to change, or the line whose cause to find.
  5. Secondary labels. Lines marked ----- tell how it came about: line 2 says names is a Vec, which is not Copy, and line 3 says the value was moved there, into roster.
  6. The help. A help line suggests a fix and shows it as an edit: + marks what to add. Here it suggests names.clone(); whether copying is the right fix is still your decision.
  7. The last lines. aborting due to 1 previous error says how many errors there were, and the final line names the command for the long explanation: rustc --explain E0382.
  8. In reading order. Read every error the same way: code and message, place, the ^^^ label, the --- labels that explain it, then help and notes. Then decide the fix yourself.

The part to read first is the primary label, the line marked ^^^^^, because it says exactly what rustc refuses; the ----- labels then explain how the code got there. Not every error has secondary labels or a suggested edit, and some carry their advice as = help: lines instead. This one, for two mutable references into the same list, has a note of that kind:

scores.rs: two mutable borrows of one list Rust · scores.rs
fn main() {
    let mut scores = vec![72, 85, 64];
    let first = &mut scores[0];
    let last = &mut scores[2];
    *first += 5;
    *last += 5;
    println!("{scores:?}");
}

Output (exit status 1)

Printed as an error (standard error)

error[E0499]: cannot borrow `scores` as mutable more than once at a time
 --> scores.rs:4:21
  |
3 |     let first = &mut scores[0];
  |                      ------ first mutable borrow occurs here
4 |     let last = &mut scores[2];
  |                     ^^^^^^ second mutable borrow occurs here
5 |     *first += 5;
  |     ----------- first borrow later used here
  |
  = help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0499`.

Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O scores.rs && ./scores

The primary label marks the second &mut scores, a secondary label the first one, and another the line that still uses the first. The = help: line names a method of the standard library made for this case, split_at_mut. Whatever the advice, rustc only suggests: here the simplest fix is to finish with first before creating last.

Checkpoint

Check: The parts of a diagnostic

  1. Question 1 of 2 In a rustc error, what does the part of the source marked with ^^^^^ show?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The exact place the error message is about

    The primary label marks what the message is about, and its text says it, such as "value borrowed here after move". Secondary labels marked ----- show the steps that led there, such as "value moved here".

  2. Question 2 of 2 For roster.rs, rustc suggests let roster = names.clone();. What is the right way to treat that help line?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: As one possible fix, to weigh against others such as borrowing or using names before the move

    rustc's suggestions show one way to compile, not the design you want. Cloning makes two lists; borrowing the list, or reading its length before handing it over, keeps one.

Every error code has a long explanation built into rustc

The error code is the most stable part of a diagnostic. Every code has a page in the Rust error codes index, and the same text is built into the compiler, so rustc --explain prints it without a network connection. The last line of every error says which command to type. Here is the explanation of the code that scores.rs received:

rustc --explain E0499 Python · explain_session.py
from session import run

# The long explanation of the error the compiler gave scores.rs.
run("""
rustc --explain E0499
""")

Output

$ rustc --explain E0499
A variable was borrowed as mutable more than once.

Erroneous code example:

```
let mut i = 0;
let mut x = &mut i;
let mut a = &mut i;
x;
// error: cannot borrow `i` as mutable more than once at a time
```

Please note that in Rust, you can either have many immutable references, or one
mutable reference. For more details you may want to read the
[References & Borrowing][references-and-borrowing] section of the Book.

[references-and-borrowing]: https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html

Example:

```
let mut i = 0;
let mut x = &mut i; // ok!

// or:
let mut i = 0;
let a = &i; // ok!
let b = &i; // still ok!
let c = &i; // still ok!
b;
a;
```

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 explain_session.py

It states the rule in one sentence, “a variable was borrowed as mutable more than once”, shows a shorter program with the same error, explains the rule behind it (many shared references, or one mutable reference), links the chapter of the Rust book that teaches it, and ends with versions that compile. The lines between triple backticks are code examples, written in Markdown as the error index pages are. Some explanations are much longer: E0382’s is over a hundred lines, with several ways to fix the error.

When the Rust project asked in its 2025 survey whether these explanations help, the result went against what the project had expected: many users find them useful. They are worth the habit, especially for a code you meet for the first time.

Checkpoint

Check: rustc --explain and the error index

  1. One question How do you read the long explanation of error E0502 on a computer without a network connection?

    Choose one answer.

    Show the answer: One question

    Answer: rustc --explain E0502

    rustc --explain prints the same text as the error code's page in the error index: the rule, a program that breaks it, and versions that compile.

Fix the first error, then check again

cargo check is the command for this loop: it runs the compiler’s checks and skips generating code, so it answers fastest. The order of fixing matters for two reasons, and this program shows the first:

typo.rs: two errors, one cause Rust · typo.rs
fn main() {
    let prices = [40, 25, 15];
    let totl: u32 = prices.iter().sum();
    let tip = total / 10;
    println!("bill {} with tip {}", total, tip);
}

Output (exit status 1)

Printed as an error (standard error)

error[E0425]: cannot find value `total` in this scope
 --> typo.rs:4:15
  |
4 |     let tip = total / 10;
  |               ^^^^^
  |
help: a local variable with a similar name exists
  |
4 -     let tip = total / 10;
4 +     let tip = totl / 10;
  |

error[E0425]: cannot find value `total` in this scope
 --> typo.rs:5:37
  |
5 |     println!("bill {} with tip {}", total, tip);
  |                                     ^^^^^
  |
help: a local variable with a similar name exists
  |
5 -     println!("bill {} with tip {}", total, tip);
5 +     println!("bill {} with tip {}", totl, tip);
  |

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0425`.

Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O typo.rs && ./typo

Two errors, both E0425, and neither points at the cause: the misspelled totl on line 3, which only the help lines name. Fixing line 3 removes both errors. Errors further down a list are often consequences of the first, so fix from the top and check again, rather than working through a long list at once.

The second reason is that the compiler checks a program in stages. The Rust compiler development guide describes them: it parses the code, resolves names, infers and checks types, and checks borrows on a later form of the program, before any code is generated. A stage that finds errors usually ends the check, so the errors of later stages stay hidden until you fix it. Step through three rounds of cargo check on one package:

Interactive · stage by stage Why fixing one error can bring another Step through the three rounds of cargo check in the session below. Watch where each round stops.
Why fixing one error can bring another, step 4 of 4. Showing Parse the code, Resolve names, Check types (passes now), Check borrows (passes now), Generate code (chai and 2 items). In focus: Generate code.fix and check againParse the codetokens and syntaxResolve namesevery name, every pathCheck typespasses nowCheck borrowspasses nowGenerate codechai and 2 items

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. Round 1. The cart parses and every name resolves, but count: u32 = cart.len() mixes types: len() returns usize. rustc reports E0308 and stops before checking borrows.
  2. Fix and check again. Fix 1 lets count keep the usize. Nothing else changed, and cargo check runs the stages again from the start.
  3. Round 2. Types pass now, so the borrow checker runs for the first time and finds E0502: cart.push needs cart while first still borrows it. The error was there all along.
  4. Round 3. Fix 2 prints first before the push. Every stage passes, so cargo check reports nothing, and cargo run goes on to generate code and run the program.

This is the session the figure follows:

Three rounds of cargo check on one package Python · rounds_session.py
from session import run

# Fix the error rustc reports, check again, and repeat until the package is clean.
run("""
cd cart
!cargo check
# fix 1: let count = cart.len(); keeps the usize that len() returns
cp ../fixes/cart_2.rs src/main.rs
!cargo check
# fix 2: print first while it is still borrowed, push afterwards
cp ../fixes/cart_3.rs src/main.rs
cargo check
cargo run
""")

Output

$ cd cart
$ cargo check
    Checking cart v0.1.0 (~/cart)
error[E0308]: mismatched types
 --> src/main.rs:4:22
  |
4 |     let count: u32 = cart.len();
  |                ---   ^^^^^^^^^^ expected `u32`, found `usize`
  |                |
  |                expected due to this
  |
help: you can convert a `usize` to a `u32` and panic if the converted value doesn't fit
  |
4 |     let count: u32 = cart.len().try_into().unwrap();
  |                                ++++++++++++++++++++

For more information about this error, try `rustc --explain E0308`.
error: could not compile `cart` (bin "cart") due to 1 previous error
[exit status 101]
# fix 1: let count = cart.len(); keeps the usize that len() returns
$ cp ../fixes/cart_2.rs src/main.rs
$ cargo check
    Checking cart v0.1.0 (~/cart)
error[E0502]: cannot borrow `cart` as mutable because it is also borrowed as immutable
 --> src/main.rs:5:5
  |
3 |     let first = &cart[0];
  |                  ---- immutable borrow occurs here
4 |     let count = cart.len();
5 |     cart.push(String::from("lassi"));
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
6 |     println!("{first} and {count} items");
  |                ----- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `cart` (bin "cart") due to 1 previous error
[exit status 101]
# fix 2: print first while it is still borrowed, push afterwards
$ cp ../fixes/cart_3.rs src/main.rs
$ cargo check
    Checking cart v0.1.0 (~/cart)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in …
$ cargo run
   Compiling cart v0.1.0 (~/cart)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in …
     Running `target/debug/cart`
chai and 2 items
now 3 items

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 rounds_session.py

The first round reported only E0308. After fix 1, the same command reported E0502, an error that had been in the code from the start, on the line right after the first. Only the third round was clean. One error became another not because the fix broke anything, but because the borrow checker could finally run.

Key idea

Fix the first error first and check again: later errors are often caused by it, and errors of later stages, such as borrow errors, appear only once the earlier stages pass.

Checkpoint

Check: Fixing errors in order

  1. Question 1 of 2 typo.rs got two E0425 errors, at lines 4 and 5. Which single edit fixes both?

    Choose one answer.

    A hint

    Neither error points at the line that causes them.

    Show the answer: Question 1 of 2

    Answer: Rename totl to total on line 3

    Two errors, one cause: the declaration on line 3 is misspelled. The help lines name totl as a similar name, which is the clue; fixing the cause removes both errors at once.

  2. Question 2 of 2 After fix 1, cargo check reported E0502, which it had not reported before. Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Borrows are checked only once the types pass, so the error had been hidden behind E0308

    rustc checks in stages: parsing, names, types, then borrows, before generating code. A stage with errors usually ends the check, so fixing type errors can uncover borrow errors that were there all along.

Warnings name their lint, and you choose its level

A warning means the code compiled but probably does not do what you meant. Every warning comes from a lint, a named check, and the warning names it. This program is meant to add up the marks of the students who passed, and it compiles with three warnings:

tally/src/main.rs: three warnings in eleven lines Rust · tally/src/main.rs
use std::fmt::Write;

// Meant to add up the marks of those who passed.
fn main() {
    let mut marks = vec![72, 35, 91];
    let pass_mark = 40;
    let total: u32 = marks.iter().sum();
    let mut report = String::new();
    write!(report, "total of passing marks: {total}");
    println!("{report}");
}
The warnings, and what cargo fix does with them Python · lints_session.py
from session import run

# Three warnings, each with its lint, and what cargo fix does about them.
run("""
cd tally
cargo check
!cargo fix
cargo fix --allow-no-vcs
cat src/main.rs
""")

Output

$ cd tally
$ cargo check
    Checking tally v0.1.0 (~/tally)
warning: variable does not need to be mutable
 --> src/main.rs:5:9
  |
5 |     let mut marks = vec![72, 35, 91];
  |         ----^^^^^
  |         |
  |         help: remove this `mut`
  |
  = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default

warning: unused variable: `pass_mark`
 --> src/main.rs:6:9
  |
6 |     let pass_mark = 40;
  |         ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_pass_mark`
  |
  = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default

warning: unused `Result` that must be used
 --> src/main.rs:9:5
  |
9 |     write!(report, "total of passing marks: {total}");
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: this `Result` may be an `Err` variant, which should be handled
  = note: `#[warn(unused_must_use)]` (part of `#[warn(unused)]`) on by default
help: use `let _ = ...` to ignore the resulting value
  |
9 |     let _ = write!(report, "total of passing marks: {total}");
  |     +++++++

warning: `tally` (bin "tally") generated 3 warnings (run `cargo fix --bin "tally" -p tally` to apply 2 suggestions)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in …
$ cargo fix
error: no VCS found for this package and `cargo fix` can potentially perform destructive changes; if you'd like to suppress this error pass `--allow-no-vcs`
[exit status 101]
$ cargo fix --allow-no-vcs
    Checking tally v0.1.0 (~/tally)
       Fixed src/main.rs (2 fixes)
warning: unused `Result` that must be used
 --> src/main.rs:9:5
  |
9 |     write!(report, "total of passing marks: {total}");
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: this `Result` may be an `Err` variant, which should be handled
  = note: `#[warn(unused_must_use)]` (part of `#[warn(unused)]`) on by default
help: use `let _ = ...` to ignore the resulting value
  |
9 |     let _ = write!(report, "total of passing marks: {total}");
  |     +++++++

warning: `tally` (bin "tally") generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in …
$ cat src/main.rs
use std::fmt::Write;

// Meant to add up the marks of those who passed.
fn main() {
    let marks = vec![72, 35, 91];
    let _pass_mark = 40;
    let total: u32 = marks.iter().sum();
    let mut report = String::new();
    write!(report, "total of passing marks: {total}");
    println!("{report}");
}

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 lints_session.py

Each note names a lint: unused_mut, unused_variables and unused_must_use, all three part of the group unused, all “on by default”, which means at the warn level. cargo fix applies the compiler’s suggestions to the files, so it refuses to run where no version control could undo its changes, unless you pass --allow-no-vcs. It then made 2 fixes and left the third, because ignoring a Result is a decision, not an edit.

Look at what the fixes did. let _pass_mark silenced a warning that was pointing at a bug: the pass mark was never used, so the total still counts the 35 of a student who failed. A warning is a question about your code, and the fixed package answers both of its real questions: it filters by the pass mark, and it builds the report with format!, which has no Result to ignore.

The level of a lint is yours to set. These are the five levels an attribute can set, from the weakest to the strongest; a sixth, force-warn, exists only as a command-line option:

Level Effect
allow The lint does nothing
expect Quiet, but warns if the lint stops firing there
warn A warning; the build goes on
deny An error; the build fails
forbid Like deny, and nothing inside may lower it

Set it for one item with an attribute such as #[expect(unused_variables, reason = "used on Windows only")], for a whole package in the [lints] table of Cargo.toml, or for every warning of a build with Cargo’s build.warnings setting, CARGO_BUILD_WARNINGS=deny on the command line:

Warnings that fail a build, and the fixed package Python · levels_session.py
from session import run

# Warnings that fail the build, then the package with both bugs fixed and an ignored Result made an error.
run("""
cd tally
!CARGO_BUILD_WARNINGS=deny cargo check
cd ../tally-fixed
cat Cargo.toml
cargo check
cargo run
""")

Output

$ cd tally
$ CARGO_BUILD_WARNINGS=deny cargo check
    Checking tally v0.1.0 (~/tally)
warning: variable does not need to be mutable
 --> src/main.rs:5:9
  |
5 |     let mut marks = vec![72, 35, 91];
  |         ----^^^^^
  |         |
  |         help: remove this `mut`
  |
  = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default

warning: unused variable: `pass_mark`
 --> src/main.rs:6:9
  |
6 |     let pass_mark = 40;
  |         ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_pass_mark`
  |
  = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default

warning: unused `Result` that must be used
 --> src/main.rs:9:5
  |
9 |     write!(report, "total of passing marks: {total}");
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: this `Result` may be an `Err` variant, which should be handled
  = note: `#[warn(unused_must_use)]` (part of `#[warn(unused)]`) on by default
help: use `let _ = ...` to ignore the resulting value
  |
9 |     let _ = write!(report, "total of passing marks: {total}");
  |     +++++++

error: `tally` (bin "tally") generated 3 warnings (run `cargo fix --bin "tally" -p tally` to apply 2 suggestions)
error: warnings are denied by `build.warnings` configuration
[exit status 101]
$ cd ../tally-fixed
$ cat Cargo.toml
[package]
name = "tally-fixed"
version = "0.1.0"
edition = "2024"

[dependencies]

# In this package an ignored Result is an error, not a warning.
[lints.rust]
unused_must_use = "deny"
$ cargo check
    Checking tally-fixed v0.1.0 (~/tally-fixed)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in …
$ cargo run
   Compiling tally-fixed v0.1.0 (~/tally-fixed)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in …
     Running `target/debug/tally-fixed`
total of passing marks: 163

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 levels_session.py

With build.warnings set to deny, the same three warnings made cargo check fail with exit status 101. The fixed package passes cleanly, prints the right total, 163, and its [lints.rust] table makes any ignored Result an error from now on.

Silencing a warning without reading it

#[allow(…)], a leading underscore or cargo fix make a warning disappear, but the warning may be the only sign of a bug, as _pass_mark was. Read what it says first; when you do mean to keep the code, prefer #[expect(…)] with a reason, which warns again if the code changes so that the lint no longer fires.

Checkpoint

Check: Lints and their levels

  1. Question 1 of 2 A warning's note says #[warn(unused_variables)] (part of #[warn(unused)]) on by default. What is unused_variables?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The name of the lint that produced the warning, in the lint group unused

    The lint's name is what you use to change its level: #[allow(unused_variables)], unused_variables = "deny" in the [lints.rust] table, or -D unused-variables on the command line.

  2. Question 2 of 2 After cargo fix, tally compiles with one warning fewer, and pass_mark has become _pass_mark. Is the program right now?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: No, the total still includes the failing mark 35, because the pass mark is still not used

    A warning is a question about the code. cargo fix answered "is this unused on purpose?" with yes, which was wrong here: the fixed package uses the pass mark in a filter and prints 163, the total of the two passing marks.

Diagnostics change their words between releases, not their codes

The wording of a diagnostic is not fixed. Rust 1.91 added a warning for functions that return a raw pointer to one of their own local variables, and its announcement shows the warning as “a dangling pointer will be produced because the local variable x will be dropped”. Here is the same warning from rustc 1.99.0:

dangling.rs: a pointer to a variable that is gone Rust · dangling.rs
// Returns the address of a local variable, which no longer exists once the function returns.
fn rate() -> *const u32 {
    let percent = 18;
    &percent
}

fn main() {
    println!("null pointer? {}", rate().is_null());
}
The warning as rustc 1.99.0 words it Python · wording_session.py
from session import run

# A warning added in Rust 1.91, as rustc 1.99.0 words it.
run("""
rustc --edition 2024 dangling.rs
./dangling
""")

Output

$ rustc --edition 2024 dangling.rs
warning: function returns a dangling pointer to dropped local variable `percent`
 --> dangling.rs:4:5
  |
2 | fn rate() -> *const u32 {
  |              ---------- return type is `*const u32`
3 |     let percent = 18;
  |         ------- local variable `percent` is dropped at the end of the function
4 |     &percent
  |     ^^^^^^^^
  |
  = note: a dangling pointer is safe, but dereferencing one is undefined behavior
  = note: for more information, see <https://doc.rust-lang.org/reference/destructors.html>
  = note: `#[warn(dangling_pointers_from_locals)]` on by default

warning: 1 warning emitted
$ ./dangling
null pointer? false

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 wording_session.py

The sentence is now “function returns a dangling pointer to dropped local variable percent”, with labels for the return type and for where the variable is dropped. The lint’s name, dangling_pointers_from_locals, stayed the same, as error codes do. So search for the code or the lint name rather than the sentence, and when a page shows an error in other words, compare its code first. Every diagnostic shown in this track comes from rustc 1.99.0; another release may word some of them differently.

Checkpoint

Check: Codes stay, words change

  1. One question A tutorial shows an error in other words than your rustc 1.99.0 prints for what looks like the same mistake. How do you check whether it is the same diagnostic?

    Choose one answer.

    Show the answer: One question

    Answer: Compare the error code or the lint name

    Rust 1.91's announcement and rustc 1.99.0 word the same warning differently, but its lint is dangling_pointers_from_locals in both. Error codes such as E0382 are just as stable.

Practise: predict the diagnostic

Each question shows a short program that does not compile. Decide which error code rustc reports and which line it points at, before you look at the answer. The explanation of each answer quotes what rustc 1.99.0 reported, and the drills you miss return before the others:

Practice that adapts to you

Practise this lesson

14 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 14 Which error does rustc report for this program, and which line does it point at?

    Read the code, then choose one answer.

    fn main() {
        let count = 1;
        count = 2;
        println!("{count}");
    }
    Show the answer: Question 1 of 14

    Answer: E0384, cannot assign twice to an immutable variable, at line 3

    rustc 1.99.0 printed "error[E0384]: cannot assign twice to immutable variable count" pointing at line 3, column 5. Writing let mut count = 1; makes the program compile and print 2.

  2. Question 2 of 14 Which error does rustc report for this program, and which line does it point at?

    Read the code, then choose one answer.

    fn main() {
        let name = String::from("Asha");
        let copy = name;
        println!("{name} {copy}");
    }
    Show the answer: Question 2 of 14

    Answer: E0382, borrow of moved value, at line 4

    rustc printed "error[E0382]: borrow of moved value: name" at line 4, column 16, where the format string uses name. The fix depends on intent: let copy = name.clone(); keeps two strings, or use name before moving it.

  3. Question 3 of 14 Which error does rustc report for this program, and which line does it point at?

    Read the code, then choose one answer.

    fn main() {
        let qty: u32 = "3";
        println!("{qty}");
    }
    Show the answer: Question 3 of 14

    Answer: E0308, mismatched types, at line 2

    rustc printed "error[E0308]: mismatched types" at line 2, column 20, the string literal. Parsing the text, "3".parse::<u32>(), returns a Result that the program must handle.

  4. Question 4 of 14 Which error does rustc report for this program, and which line does it point at?

    Read the code, then choose one answer.

    fn main() {
        let mut marks = vec![72, 35];
        let first = &marks[0];
        marks.push(91);
        println!("{first}");
    }
    Show the answer: Question 4 of 14

    Answer: E0502, a mutable borrow while an immutable one is in use, at line 4

    rustc printed "error[E0502]: cannot borrow marks as mutable because it is also borrowed as immutable" at line 4. As in the cart rounds, moving the println! above the push fixes it.

  5. Question 5 of 14 Which error does rustc report for this program, and which line does it point at?

    Read the code, then choose one answer.

    fn main() {
        let total = 40 + 25;
        println!("{totl}");
    }
    Show the answer: Question 5 of 14

    Answer: E0425, cannot find value totl, at line 3

    rustc printed "error[E0425]: cannot find value totl in this scope" at line 3, column 16. A variable named in a format string must exist like any other name.

  6. Question 6 of 14 Where else can you read exactly the text that rustc --explain E0499 prints?

    Choose one answer.

    Show the answer: Question 6 of 14

    Answer: On the error's page in the Rust error codes index, doc.rust-lang.org/error_codes/E0499.html

    Each code has a page in the error index with the same text that the compiler carries. The page is easy to search and link; rustc --explain works anywhere, without a browser.

  7. Question 7 of 14 What does the explanation of E0499 contain?

    Choose one answer.

    Show the answer: Question 7 of 14

    Answer: The rule in one sentence, a program that breaks it, and versions that compile

    rustc --explain E0499 states that a variable was borrowed as mutable more than once, shows a short program with the error, explains the borrowing rule and gives versions that compile.

  8. Question 8 of 14 cargo check lists twelve errors after a large change. Where do you start?

    Choose one answer.

    Show the answer: Question 8 of 14

    Answer: With the first error, then check again

    Fix from the top and run cargo check again. Errors that were consequences go away, and errors of later stages, such as borrow errors, may appear for the first time.

  9. Question 9 of 14 Why is cargo check, rather than cargo build, the command for the fix-and-check-again loop?

    Choose one answer.

    Show the answer: Question 9 of 14

    Answer: It runs all the compiler's checks and skips generating code, so it answers fastest

    cargo check gives the compiler's verdict without the cost of building a program. Run cargo build or the tests at the end, because a few errors only appear during code generation.

  10. Question 10 of 14 What does a warning from rustc mean, compared with an error?

    Choose one answer.

    Show the answer: Question 10 of 14

    Answer: The code compiled, but probably does not do what you meant

    Errors stop the build; warnings report code that compiles but looks wrong, such as an unused variable or an ignored Result. Each one names its lint, whose level you can raise to make it an error.

  11. Question 11 of 14 You want any ignored Result in one package to fail its build, without changing other packages. What do you add?

    Choose one answer.

    Show the answer: Question 11 of 14

    Answer: unused_must_use = "deny" in the [lints.rust] table of that package's Cargo.toml

    The [lints] table of Cargo.toml applies to the package's own code and not to its dependencies. deny turns the lint into an error there, while other packages keep the default.

  12. Question 12 of 14 Why prefer #[expect(unused_variables, reason = "…")] to #[allow(unused_variables)] for a variable you keep on purpose?

    Choose one answer.

    Show the answer: Question 12 of 14

    Answer: expect warns when the lint stops firing, so the attribute cannot outlive its reason unnoticed

    allow silences a lint for good. expect silences it too, but if the code changes so that the lint would no longer fire, the compiler says so, and the reason tells the next reader why the attribute is there.

  13. Question 13 of 14 Which command made the three warnings of tally fail the check, with exit status 101?

    Choose one answer.

    Show the answer: Question 13 of 14

    Answer: CARGO_BUILD_WARNINGS=deny cargo check

    With build.warnings set to deny, Cargo prints the warnings and then "warnings are denied by build.warnings configuration", and the command fails. It applies to every lint warning of the build, while a [lints] table picks single lints.

  14. Question 14 of 14 Which part of a diagnostic stays the same from one Rust release to the next?

    Choose one answer.

    Show the answer: Question 14 of 14

    Answer: Its error code or lint name

    Search and compare by code or lint name. The words around them may change with any release, which is why every diagnostic in this track is shown as rustc 1.99.0 printed it.

Here is what rustc 1.99.0 printed for the five drill programs, the first two lines of each, the code and the place:

The drills as rustc 1.99.0 reported them Python · drills_session.py
from session import run

# The first two lines of what rustc 1.99.0 printed for each drill: the error and where it points.
run("""
rustc --edition 2024 drills/drill_1.rs 2>&1 | head -n 2
rustc --edition 2024 drills/drill_2.rs 2>&1 | head -n 2
rustc --edition 2024 drills/drill_3.rs 2>&1 | head -n 2
rustc --edition 2024 drills/drill_4.rs 2>&1 | head -n 2
rustc --edition 2024 drills/drill_5.rs 2>&1 | head -n 2
""")

Output

$ rustc --edition 2024 drills/drill_1.rs 2>&1 | head -n 2
error[E0384]: cannot assign twice to immutable variable `count`
 --> drills/drill_1.rs:3:5
$ rustc --edition 2024 drills/drill_2.rs 2>&1 | head -n 2
error[E0382]: borrow of moved value: `name`
 --> drills/drill_2.rs:4:16
$ rustc --edition 2024 drills/drill_3.rs 2>&1 | head -n 2
error[E0308]: mismatched types
 --> drills/drill_3.rs:2:20
$ rustc --edition 2024 drills/drill_4.rs 2>&1 | head -n 2
error[E0502]: cannot borrow `marks` as mutable because it is also borrowed as immutable
 --> drills/drill_4.rs:4:5
$ rustc --edition 2024 drills/drill_5.rs 2>&1 | head -n 2
error[E0425]: cannot find value `totl` in this scope
 --> drills/drill_5.rs:3:16

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 drills_session.py

Interview questions

Warm-up: what are the parts of a rustc error message?

A rustc diagnostic starts with its level and code, such as error[E0382], and a one-line message. The --> line gives the file, line and column. Under it the compiler shows the source: ^^^^^ marks the primary label, the exact place the message is about, and ----- lines mark secondary labels that explain how the code got there, such as where a value was moved. Notes and help lines follow: = note: adds facts, and help: suggests a fix, often as an edit marked with + and -. The last lines count the errors and name rustc --explain with the code, which prints the long explanation, the same text as the error index page. For a warning, a note also names its lint, which tells you how to change its level.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    After a refactor, a junior colleague's branch has 40 compiler errors. They are fixing them from the bottom of the list up and running cargo build after each fix. How do you coach them through it?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    Your codebase has 300 #[allow(...)] attributes, and nobody knows which are still needed. What policy would you set for silencing lints, and how would you clean up the existing ones?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A rustc diagnostic has a level and code, a message, a location, a primary ^^^ label, secondary --- labels, and notes and help; read the primary label first, and treat a suggested edit as a suggestion.
  • rustc --explain prints the long explanation of an error code without a network connection, the same text as the error index.
  • Fix the first error and check again with cargo check: later errors are often consequences, and borrow errors appear only after the type errors are gone.
  • Every warning names its lint; set its level with an attribute, the [lints] table of Cargo.toml or Cargo’s build.warnings, and read a warning before cargo fix or allow silence it.
  • The words of a diagnostic can change between releases while its code and lint name stay, so search by code.

Terms in this lesson

diagnostic
A message from the compiler about your code: an error, which stops the build, or a warning, which does not. It gives a level, often a code such as E0382, the place, labels on the source and, when it can, help.
lint
A named check that the compiler or Clippy runs on code that compiles, such as unused_variables. Its level, allow, warn, deny or forbid, decides whether it is silent, a warning or an error.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.