Compiler error decoder ring
Rust compiler errors are precise. They almost always tell you exactly what is wrong. The trouble is that agents read the error, find the cheapest patch that silences it, and call it done. Often that patch is the wrong fix.
This page lists the eight errors agents fumble most, the lazy patch each one tends to produce, and what the real fix actually looks like.
E0382 — use of moved value
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 moveWhat Rust is saying: You gave away ownership of s to take. You no longer have it.
// Lazy: clone to make both call sites happy.
take(s.clone());
println!("{}", s);// Real: does take actually need ownership?
fn take(s: &str) { /* ... */ }
take(&s);
println!("{}", s);Almost always, the function that "took" the value did not need to. Change the parameter to &T and the move disappears. Clone only when you genuinely need two independent owned copies.
E0502 — conflicting borrow
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 hereWhat Rust is saying: You held a reference into the vector, then asked the vector to grow. Growing might reallocate, which would invalidate your reference. Rust refuses to let you read it after.
// Lazy: clone first so the borrow ends.
let first = v[0].clone();
v.push(1);
println!("{}", first);// Real: re-order so the borrow ends naturally.
v.push(1);
let first = &v[0];
println!("{}", first);
// Or: copy out the value cheaply (if Copy).
let first = v[0];
v.push(1);The compiler is asking "do you really mean to read the old slot after you might have moved the data?" Restructure so the answer is "no."
E0277 — trait bound not satisfied
error[E0277]: the trait bound `Foo: Debug` is not satisfied
|
4 | println!("{:?}", foo);
| ^^^ `Foo` cannot be formatted using `{:?}`
|
help: add `#[derive(Debug)]` to `Foo`What Rust is saying: The type does not implement the trait the call site requires.
// Lazy: drop the call site.
// println!("{:?}", foo); // removed// Real: derive it.
#[derive(Debug)]
struct Foo { /* ... */ }Agents sometimes "fix" this by removing the call site that needed the trait, which deletes useful debugging output. Add the derive. If the type contains something that itself does not implement Debug (a closure, a raw pointer, an opaque foreign type), implement Debug manually rather than removing it.
E0106 — missing lifetime specifier
error[E0106]: missing lifetime specifier
|
2 | fn name(&self) -> &str {
| ^ expected named lifetime parameterWhat Rust is saying: This reference borrows from something, and I do not know what.
// Lazy: 'static lifetime, often achieved by leaking.
fn name(&self) -> &'static str {
Box::leak(self.name.clone().into_boxed_str())
}// Real: borrow from self.
fn name(&self) -> &str {
&self.name
}
// Lifetime elision handles this; explicit form:
fn name<'a>(&'a self) -> &'a str {
&self.name
}'static says "this reference lives forever." That is rarely true. Real code borrows from self, from a function argument, or returns an owned String. Pick one of those.
E0599 — method not found
error[E0599]: no method named `iter` found for type `MyCollection`
|
4 | for x in c.iter() {
| ^^^^ method not found in `MyCollection`What Rust is saying: The type does not have that method, or the trait that provides the method is not imported.
// Lazy: change the call site to something that "works."
let v: Vec<_> = c.into_iter().collect(); // wasteful
for x in v.iter() { /* ... */ }// Real: import the trait that provides the method.
use std::iter::IntoIterator;
// Or: implement IntoIterator for &MyCollection so &c.into_iter() works.
// Or: add fn iter(&self) -> impl Iterator<Item = &T> on the type.Often this is a missing use line. Sometimes it is a missing trait impl. Almost never is it solved by routing around the type.
E0308 — type mismatch
error[E0308]: mismatched types
|
4 | let n: u64 = 5_i32;
| --- ^^^^^ expected `u64`, found `i32`
| |
| expected due to thisWhat Rust is saying: I expected one type, got another. Rust does not implicitly convert numeric types.
// Lazy: cast with as and ignore overflow.
let n: u64 = 5_i32 as u64;// Real: pick the source type to match, or use a safe conversion.
let n: u64 = 5;
// Or, for fallible narrow-to-wide:
let n: u64 = u64::from(5_i32 as u32); // be explicit about sign handling
// Or, for fallible wide-to-narrow:
let n: u64 = u64::try_from(some_i32)?;as does silent truncation and sign-flipping. Use From, TryFrom, or try_into() when the conversion can fail. Reach for as only for unambiguous widening of unsigned numbers.
E0507 — cannot move out of borrowed content
error[E0507]: cannot move out of `self.name` which is behind a shared reference
|
4 | let n = self.name;
| ^^^^^^^^^ move occurs because `self.name` has type `String`What Rust is saying: You have a &self, but you tried to move a field out of it.
// Lazy: clone.
let n = self.name.clone();// Real: borrow.
let n: &str = &self.name;
// Or, if the function genuinely needs ownership, change &self to self:
fn into_name(self) -> String { self.name }If the consumer needs ownership, change the receiver to self. If it just needs to read, borrow. Cloning is the last resort.
E0716 — temporary value dropped while borrowed
error[E0716]: temporary value dropped while borrowed
|
3 | let s: &str = &String::from("hi");
| ^^^^^^^^^^^^^^^^^^^- temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
4 | println!("{}", s);
| - borrow later used hereWhat Rust is saying: You borrowed something that has no name and therefore no lifetime. The compiler dropped it at the end of the statement.
// Lazy: Box::leak the string.
let s: &str = Box::leak(String::from("hi").into_boxed_str());// Real: bind the temporary to a name.
let owned = String::from("hi");
let s: &str = &owned;The fix is to give the temporary a name. Now it lives until its scope ends, and the borrow is valid.
The meta-pattern
Every compiler error is the compiler asking a precise question. Agents tend to interpret each error as "make this go away." The orchestrator's job is to ask the same question the compiler is asking, then answer it deliberately.
The agent slowing down to interpret the error is the difference between "code that silences warnings" and "code that solves the underlying problem."