Skip to content
Select theme

How to read a Rust compiler error message

Short answer: read it top to bottom, in four parts. The headline — error[E0308]: mismatched types — says what went wrong. The --> line says where. The snippet with its labels says why. The help: block says what to try first. Most messages contain their own fix; the skill is reading them in that order instead of flinching at the wall of carets.

A broken program, and what the compiler says

Section titled “A broken program, and what the compiler says”

Here is a program with one mistake: the annotation promises an unsigned integer, the expression is a string.

Building it with cargo build prints (with cargo’s own wrapping lines removed — they are covered at the end of this article):

error[E0308]: mismatched types
--> src/main.rs:2:22
|
2 | let count: u32 = "3";
| --- ^^^ expected `u32`, found `&str`
| |
| expected due to this
For more information about this error, try `rustc --explain E0308`.

Everything the compiler wants to say is in these lines. The next four sections take them one at a time.

The first line carries two facts:

  • E0308 — the error code. It is stable across compiler versions, which makes it the thing to paste into a search engine, or into rustc --explain (see the end of this article). Thousands of people have hit the exact same code before you.
  • mismatched types — the short description. It is a summary, not a proof: sometimes the phrase is vague because the underlying problem has several causes. When the phrase and your mental model disagree, trust the code and the spans, not the phrase.
--> src/main.rs:2:22

That is file:line:column. Column 22 is the first character of "3" — the compiler points at where the offending expression starts, not at the whole statement. The path is relative to the directory that holds your Cargo.toml, so src/main.rs in a default binary crate is the file you have open in your editor.

The snippet reproduces the offending line with a | gutter, then annotates it from below:

2 | let count: u32 = "3";
| --- ^^^ expected `u32`, found `&str`
| |
| expected due to this

Each run of ^ or - is a span, and the text after it is a label. Read them like this:

  • ^^^ under "3" is the primary span — the thing that is wrong. Its label states the mismatch directly: expected u32, found &str.
  • --- under u32 is a secondary span — context for the primary one, connected by the | below it. Its label, expected due to this, points at where the expected type was decided: the annotation you wrote on line 2.

When a message has several spans, the labels together read like a sentence the compiler is writing about your code: this moved here, so it cannot be used there; this is i32, but this function returns &str. The primary span is the one attached to the headline error; the secondary spans answer “why?” for it. The chapter’s next article, How to fix error E0382: borrow of moved value, walks through a message with three spans and a help block — the same reading order applies.

Part 4 — the help block: what to try first

Section titled “Part 4 — the help block: what to try first”

Warnings have the same anatomy, which makes them good practice for reading errors. This program compiles and runs fine — it prints Hello! — but it still produces a diagnostic:

warning: unused variable: `name`
--> src/main.rs:2:9
|
2 | let name = "Ada";
| ^^^^ help: if this is intentional, prefix it with an underscore: `_name`
|
= note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default
warning: 1 warning emitted

Two kinds of extra lines appear here, and they are worth telling apart:

  • help: — a suggestion you can act on. Here: rename the variable to _name, which keeps it while telling the compiler the silence is deliberate. Suggestions sometimes include the exact code to write, spelled out inline.
  • = note: — context, not advice. This one names the lint that fired (unused_variables) and that it is on by default. Notes explain why the compiler said anything at all; they rarely tell you what to do.

A warning does not stop the build — the program still ran and printed Hello!. An error does: the exit code is non-zero and no binary is produced. Otherwise the reading order is identical.

The last line of every error is itself an instruction:

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

Run it:

Terminal window
rustc --explain E0308

The output starts like this — a one-paragraph summary, then worked examples:

Expected type did not match the received type.
Erroneous code examples:

…followed by three examples of code that triggers the error, annotated with the same expected/found labels you saw above, and a closing paragraph: “This error occurs when an expression was used in a place where the compiler expected an expression of a different type.”

Every error code has an explanation like this one, and all of them are also collected in the error code index in the official Rust documentation. Between --explain and the spans, almost no error requires guessing.

cargo build wraps the diagnostic in its own lines — Compiling c v0.1.0 (...) above it, and this below:

error: could not compile `c` (bin "c") due to 1 previous error

The name in the backticks is the crate’s name — yours will be whatever you passed to cargo new. Those wrapping lines are about cargo and the build, not about your code. The four parts to read — headline, arrow line, snippet, help — are exactly what rustc printed, unchanged. Read the diagnostic; ignore the wrapping.