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:

  1. error[E0382] — error code. Look it up at https://doc.rust-lang.org/error-index.html or run rustc --explain E0382 for a longer explanation.
  2. Title — one-line summary of what's wrong.
  3. Source location — file, line, column, and the bit of code that triggered the error.
  4. Inline labels — annotations on the offending lines explaining what each part contributes.
  5. note: and help: 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.

CategoryExample errorsCause
TypeE0308 mismatched types, E0277 trait boundThe value's type does not match what the function or operation expects.
OwnershipE0382 use after move, E0507 cannot move out, E0716 temporary droppedRust's ownership rules are not satisfied.
LifetimeE0106 missing lifetime, E0621 explicit lifetime required, E0597 borrowed value does not live long enoughThe compiler can't prove how long a reference is valid.
Trait / importE0599 method not found, E0432 unresolved importA 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 here

The 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 E0502

This prints a multi-paragraph explanation with example code. For learning, the explanations are excellent.

cargo clippy

Clippy 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:

  1. Read the labels carefully (not just the title).
  2. Run rustc --explain on the error code.
  3. Search the exact error text.
  4. Build a tiny isolated repro of the same shape.
  5. 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

ErrorPlain English
cannot borrow X as mutable, as it is also borrowed as immutableTwo refs exist when the rules allow only one. Re-order or shorten scopes.
value borrowed here after moveOwnership transferred. You no longer have the original. Change the parameter to a borrow, or accept the consequence.
borrowed value does not live long enoughA reference outlives the thing it borrows from. Bind the temporary to a name, or change ownership.
the trait bound T: Trait is not satisfiedThe type does not implement the trait. Add a derive, import the trait, or implement it manually.
mismatched types: expected X, found YThe 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 referenceYou 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.