Common Rust Compile Errors Explained: Borrow Checker and Beyond
Most Rust compile errors are the borrow checker teaching ownership rules. Read the error's "help" section — it usually contains the exact fix, including suggested code.
E0382: Use of Moved Value
let s = String::from("hello");
let t = s; // s is moved into t
println!("{}", s); // error[E0382]: borrow of moved value: 's'Why: String owns heap memory; copying the pointer would double-free. The assignment transfers ownership.
Fixes, by intent: (1) need both to keep working → clone: let t = s.clone();; (2) only need to read s → pass a reference: let t = &s;; (3) function consumed it → change the signature to take &str instead of String. The compiler literally suggests "consider borrowing here" and "consider cloning" — the choice between them is a design decision about who owns the data.
E0502 / E0499: Borrow Conflicts
let mut v = vec![1, 2, 3];
let first = &v[0];
v.push(4); // error[E0502]: cannot borrow 'v' as mutable
println!("{}", first);Why: the rule is "any number of immutable borrows OR exactly one mutable borrow" — never both at once. push may reallocate the buffer, invalidating first.
Fixes: (1) end the immutable borrow before mutating — scope it: let first = v[0]; (copy the value out, since i32 is Copy); (2) restructure so reads and writes are in separate phases; (3) if you genuinely need shared mutation, that's what RefCell/Mutex are for — reach for them deliberately, not as a first reflex.
E0499 is the two-mutable-borrows variant: let a = &mut v; let b = &mut v; — same fix family: separate the scopes or use interior mutability.
E0596: Cannot Borrow as Mutable
fn add(v: &Vec<i32>) { v.push(1); } // error[E0596]: cannot borrow '*v' as mutableFix: the parameter must be &mut Vec<i32> and the caller must have a mutable binding. Rust made mutability part of the type on purpose — the signature is a contract about who can change what.
E0308: Mismatched Types
let x: i32 = "5"; // expected 'i32', found '&str'The most common real-world instances: (1) String vs &str — fix with &s or s.as_str(); (2) integer widths (i32 vs usize when indexing — cast with as usize); (3) missing Ok(...)/Some(...) wrapper in a function returning Result/Option. The error message shows "expected X, found Y" with the exact line — read both types, the mismatch is always legible.
E0507: Cannot Move Out of Borrowed Content
fn take(v: &Vec<String>) {
for s in v { drop(s); } // error: cannot move out of 'v'
}Fix: iterate by reference — for s in v { /* s: &String */ } is already by-ref; use &s when passing on; clone only if you truly need owned values. For struct fields: borrow (&self.name) or take with std::mem::take when replacing with a default.
Lifetime Errors: E0106, E0597
fn longest(a: &str, b: &str) -> &str { // error[E0106]: missing lifetime specifier
if a.len() > b.len() { a } else { b }
}Why: the returned reference must be tied to one of the inputs; the compiler needs to know which. Fix: name it — fn longest<'a>(a: &'a str, b: &'a str) -> &'a str. The mental model that ends lifetime confusion: a lifetime is "how long this reference is valid", and you are telling the compiler that the output lives as long as the shorter of the inputs. E0597 ("does not live long enough") is the related error when a reference outlives its owner — fix by extending the owner's scope or converting to owned data.
Error-Handling Compile Errors
fn read() -> Result<String, std::io::Error> {
let s = std::fs::read_to_string("f.txt")?; // OK inside Result fn
Ok(s)
}Common cases: (1) ? in a function returning Option but the operation returns Result — convert or change the return type; (2) using ? in main without a compatible return type — declare fn main() -> Result<(), Box<dyn Error>>; (3) error types don't convert — implement From or use .map_err().
The Workflow That Beats Googling
- Read the error code (E0382) and the two sentence explanation.
- Read the "help:" block — Rust includes suggested fixes with span-accurate code.
- Run
cargo explain E0382(cargo-explain-external) or look up the code in the rustc error index for the long-form explanation with examples. - Distinguish "correct code rejected" (restructure) from "design decision needed" (clone vs reference vs owned — a real choice about performance and API).
- If fighting the borrow checker for an hour: the design usually wants owned data or a different data flow, not another
clone().