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.
fn main() { let count: u32 = "3"; println!("{count}");}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.
Part 1 — the headline: what broke
Section titled “Part 1 — the headline: what broke”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 intorustc --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.
Part 2 — the arrow line: where
Section titled “Part 2 — the arrow line: where” --> src/main.rs:2:22That 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.
Part 3 — the snippet: why
Section titled “Part 3 — the snippet: why”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 thisEach 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: expectedu32, found&str.---underu32is 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:
fn main() { let name = "Ada"; println!("Hello!");}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 emittedTwo 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 footer line is a command
Section titled “The footer line is a command”The last line of every error is itself an instruction:
For more information about this error, try `rustc --explain E0308`.Run it:
rustc --explain E0308The 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.
Under cargo, the message is the same
Section titled “Under cargo, the message is the same”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 errorThe 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.
Related
Section titled “Related”- How to fix error E0382: borrow of moved value — a worked example of reading a longer message with three spans, a
= note:and ahelp:block that proposes the exact fix.