Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.