Compiler errors
Rust's compiler is one of the best in any language. It tells you what's wrong with precision, often suggests the fix, and refuses to compile code that would crash at runtime in other languages. Learning to read its errors is most of what "learning Rust" really means.
For the orchestrator's view of how agents fumble specific errors and their lazy fixes, see the error decoder. This page is the conceptual primer.
The shape of an error
error[E0382]: borrow of moved value: `s`
--> src/main.rs:5:20
|
3 | let s = String::from("hello");
4 | take(s);
| - value moved here
5 | println!("{}", s);
| ^ value borrowed here after move
|
note: consider changing this parameter type in function `take`
to borrow instead if owning the value isn't necessary
--> src/main.rs:1:10
|
1 | fn take(s: String) {
| ^^^^^^Five parts:
error[E0382]— error code. Look it up athttps://doc.rust-lang.org/error-index.htmlor runrustc --explain E0382for a longer explanation.- Title — one-line summary of what's wrong.
- Source location — file, line, column, and the bit of code that triggered the error.
- Inline labels — annotations on the offending lines explaining what each part contributes.
note:andhelp:sections — sometimes the compiler suggests a fix. Read these but evaluate before applying.
The four big categories
Most Rust errors fall into one of four buckets. Naming the category accelerates the fix.
| Category | Example errors | Cause |
|---|---|---|
| Type | E0308 mismatched types, E0277 trait bound | The value's type does not match what the function or operation expects. |
| Ownership | E0382 use after move, E0507 cannot move out, E0716 temporary dropped | Rust's ownership rules are not satisfied. |
| Lifetime | E0106 missing lifetime, E0621 explicit lifetime required, E0597 borrowed value does not live long enough | The compiler can't prove how long a reference is valid. |
| Trait / import | E0599 method not found, E0432 unresolved import | A trait or item is missing or not in scope. |
When you see an error, name the category first. The fix follows from the category.
How to actually read one
The trick: read the labels, not just the title.
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
|
2 | let first = &v[0];
| - immutable borrow occurs here
3 | v.push(1);
| ^^^^^^^^^ mutable borrow occurs here
4 | println!("{}", first);
| ----- immutable borrow later used hereThe title says "you can't do this." The labels tell you the actual story:
- Line 2: an immutable borrow begins.
- Line 3: a mutable borrow conflicts with it.
- Line 4: the immutable borrow is still alive here.
Now you know exactly what to change. Move the push before the borrow, end the borrow earlier, or restructure.
Use rustc --explain and cargo clippy
rustc --explain E0502This prints a multi-paragraph explanation with example code. For learning, the explanations are excellent.
cargo clippyClippy goes beyond errors. It flags warning-grade issues: redundant code, unidiomatic patterns, easy performance wins. Treat its suggestions seriously.
The error you cannot dismiss
Sometimes the compiler says something you do not understand. The right response:
- Read the labels carefully (not just the title).
- Run
rustc --explainon the error code. - Search the exact error text.
- Build a tiny isolated repro of the same shape.
- Ask an LLM to explain, then verify: do not just accept the answer.
The wrong response: silence the compiler. #[allow(...)], as casts to dodge type errors, Box::leak to dodge lifetimes, .unwrap() to dodge Result. All of these compile. None of them solve the underlying problem.
When clippy disagrees
Clippy has many lints. Sometimes the project's style genuinely conflicts with one. The right escape hatch:
#[expect(clippy::single_match)] // not #[allow]
match expr {
Variant => action(),
_ => {}
}#[expect] is like #[allow], except: if the lint stops firing (you removed the offending code), #[expect] causes a compile error. Dead lint suppressions cannot accumulate.
Sail's workspace policy denies plain #[allow] exactly so that every suppression uses #[expect] and stays self-cleaning.
A few specific errors and their plain-English meanings
| Error | Plain English |
|---|---|
cannot borrow X as mutable, as it is also borrowed as immutable | Two refs exist when the rules allow only one. Re-order or shorten scopes. |
value borrowed here after move | Ownership transferred. You no longer have the original. Change the parameter to a borrow, or accept the consequence. |
borrowed value does not live long enough | A reference outlives the thing it borrows from. Bind the temporary to a name, or change ownership. |
the trait bound T: Trait is not satisfied | The type does not implement the trait. Add a derive, import the trait, or implement it manually. |
mismatched types: expected X, found Y | The value's type is wrong for the context. Convert, or change the context. |
method not found in T`` | The method doesn't exist on this type, or the trait that provides it is not in scope. Add use SomeTrait;. |
cannot move out of *self which is behind a shared reference | You have &self and tried to take ownership of a field. Either borrow the field, or change &self to self. |
Memorize these seven; they cover most day-to-day compiler chatter.