Appendix B: Reading Compiler Errors as Design Feedback
The argument running through this book is that the Rust compiler mostly asks design questions. This appendix makes that concrete: the errors you will meet most often, what each one is actually asking, and the menu of answers.
Two habits are worth building before the list. First, read the whole message —
the note and help lines below the error usually name the fix, and the first line
alone is the least useful part. Second, run rustc --explain E0502 (or any
code), which prints a page-long explanation with examples.
Ownership
E0382 — use of moved value
What it says: a value was moved on one line and used on a later one.
What it is asking: who owns this now? You handed responsibility to someone else and then carried on as though you had not.
The answers: if the other side only needed to read it, change the signature to
&T and stop moving it. If both sides need it at different times, restructure so
they take turns. If both genuinely need independent values, clone, and be able to
say why. If both need to refer to the same logical thing, give it an identifier
and let one place own the storage. If the value must be owned by a long-lived
structure and referred to from several places, that is the chapter 12 case.
E0507 — cannot move out of borrowed content
What it says: you tried to take a field or an element out through a & or
&mut.
What it is asking: the caller still owns this and is entitled to find it whole. What should be left in the hole?
The answers: clone the field if you need an owned copy. Use
std::mem::take or std::mem::replace to swap something in, which is the right
answer for state machines. Change the method to take self by value if it is
consuming the value anyway. Or return a reference instead of an owned value, if
the caller only needs to look.
E0505 — cannot move out of a value because it is borrowed
What it is asking: something is still looking at this. The move would leave a reference pointing at nothing.
The answer: end the borrow first, usually by moving the line that uses it above the move, or by extracting what you need into an owned value.
Borrowing
E0502 — cannot borrow as mutable because it is also borrowed as immutable
What it says: a read and a write overlap.
What it is asking: do these two accesses really need to happen at the same time?
The answers: most often they do not, and reading the value you need into a local before the mutation ends the borrow. If the accesses are to different fields, use the fields directly rather than going through methods that borrow the whole struct. If they are to different fields through methods, consider splitting the struct. If they must interleave over the same data, the design needs indices or interior mutability, in that order of preference.
E0499 — cannot borrow as mutable more than once
What it is asking: which of these two writers should be the writer?
The answers: the same menu as E0502. Additionally, if you are inside a loop holding a mutable borrow across iterations, check whether the borrow can be re-taken each time instead.
E0596 — cannot borrow as mutable, not declared as mutable
What it is asking: did you mean to change this?
The answer: add mut to the binding, or to the parameter type, and if adding
it feels wrong, that is information — a value nobody declared mutable is usually
one nobody intended to change.
Lifetimes
E0597 — borrowed value does not live long enough
What it says: a reference outlives what it points at.
What it is asking: which of these two should live longer?
The answers: declare the referent in an outer scope, so it outlives the reference. Or make the holder own its data instead of borrowing, which is the right answer whenever the holder is stored rather than temporary. Or restructure so the borrow is used where the data lives rather than carried out of that scope.
E0515 — cannot return reference to local variable
What it is asking: you built this inside the function and are trying to hand out a view of it, but it dies when the function returns.
The answers: return the owned value. Or take a destination parameter to write into. Or, if the thing being referenced came from an argument, return a reference derived from that argument instead and let elision or an explicit lifetime say so.
E0106 — missing lifetime specifier
What it is asking: which input does the returned reference borrow from?
The answers: decide, then annotate. If the answer is “neither, it is something
I constructed”, return an owned value. Resist the temptation to try 'a in
various positions until it builds; an over-strict signature compiles and
constrains every caller forever.
E0373 — closure may outlive the current function
What it is asking: this closure borrows a local and is going somewhere the local will not follow, usually another thread.
The answers: add move to make the closure own its captures. If it must
borrow, use std::thread::scope, which guarantees the threads end before the
scope does. If it captures something that must be shared, Arc it.
Types and traits
E0308 — mismatched types
What it is asking: usually nothing deep, and occasionally something important.
The shallow cases are a missing &, an extra *, a String where a &str was
wanted, or a u32 where a usize was. The one worth pausing on is a mismatch
between two newtypes, or between a wrapped and an unwrapped value, because that
is the type system catching the mix-up it was introduced to catch.
Check the expected and found types carefully when generics are involved; the message names them in full, and the difference is often one type parameter deep.
E0277 — the trait bound is not satisfied
What it says: a type does not have a capability something required.
What it is asking: should this type gain the capability, or should the requirement be relaxed?
The answers: implement or derive the trait, if it makes sense for the type.
Convert to a type that has it. Or relax the bound, if the function does not
really need what it asked for. When the trait is one you have never heard of —
FromIterator, TryInto, Pattern — the help text usually names an
implementation that exists and a conversion that gets you to it.
A special case worth recognizing: the trait Sized is not implemented means you
are trying to use a value whose size is unknown at compile time, typically a
dyn Trait or a str without a pointer in front of it. The answer is a Box,
an Rc, or a reference.
E0599 — no method named X found
What it is asking: the method might be in a trait that is not in scope.
The answers: check whether you need a use for the trait, which is the most
common cause and is why crates ship a prelude. Check whether you are calling a
method that exists on T while holding a &T that needs dereferencing, or vice
versa. And check whether the method exists only under a bound your type does not
satisfy, which the error will usually say.
E0038 — the trait cannot be made into an object
What it says: you wrote dyn Trait for a trait that is not object safe.
What it is asking: this trait has a method that cannot go in a vtable, usually a
generic method or one returning Self.
The answers: move the offending method behind where Self: Sized, which keeps
it available on concrete types and excludes it from the object. Return
Box<Self> instead of Self. Split the trait into an object-safe part and a
rest. Or use generics instead of trait objects, if a heterogeneous collection was
not what you needed.
The general method
When an error does not match anything above, the loop that works is the same one chapter 1 described.
Read the message, all of it, including the notes. Then say out loud what the compiler believes: it thinks this reference will outlive the data, or it thinks two things want to write to this. Then ask whether it is right. It usually is, and the case where it is right is a bug you have not found yet rather than an inconvenience.
If it is right, the question is which of the design answers above applies. If you believe it is wrong, the useful move is to narrow the example until you have a dozen lines that still fail, at which point either the misunderstanding becomes obvious or you have a minimal reproduction worth asking about.
What does not work is trying syntax until the error goes away. Annotations added
by guesswork produce signatures that constrain callers for reasons nobody
remembers, and clone added by guesswork produces programs that are slower and
no clearer. The error was a question. It is worth answering.