rust-errors-borrow-checker
How to read and fix the six most common borrow-checker errors: E0382, E0502, E0596, E0499, E0500, E0716. Every fix patches the root cause (ownership shape, scope, mutability declaration), not the symptom. .clone() is the last resort, not the first.
Cross-references: [[rust-syntax-ownership]] (move semantics), [[rust-syntax-borrowing]] (&T vs &mut T rules, NLL, split borrows), [[rust-syntax-lifetimes]] (lifetime annotations on borrows), [[rust-errors-lifetimes]] (E0106 / E0623 / E0495 / E0700 / E0759), [[rust-agents-compile-fix]] (orchestration recipe for any rustc error).
When to use this skill
- Compiler reports one of E0382, E0502, E0596, E0499, E0500, E0716 (or the post-NLL un-numbered variants that print the same message).
- User asks "why can't I use this variable after I passed it / assigned it / matched on it".
- User asks "why does Rust forbid
&and&muttogether". - User pastes a borrow-checker message and asks for a fix.
- User reaches for
.clone()as the default escape hatch.
If the message contains "lifetime" or 'a / 'static symbols, use [[rust-errors-lifetimes]] instead.
Decision tree: which error, which fix family
+--- rustc error message ---+
| |
| "use of moved value" | -> E0382 -> see "E0382" section. Fix family: avoid the move, borrow instead, or clone if data is small.
| |
| "cannot borrow ... as | -> E0502 -> see "E0502" section. Fix family: tighten the shared-borrow scope (NLL).
| mutable because it is |
| also borrowed as |
| immutable" |
| |
| "cannot borrow ... as | -> E0596 -> see "E0596" section. Fix family: declare `mut`, change receiver to `&mut self`.
| mutable, as it is not |
| declared as mutable" |
| |
| "cannot borrow ... as | -> E0499 -> see "E0499" section. Fix family: scope-tighten or split borrow across disjoint paths.
| mutable more than once |
| at a time" |
| |
| "closure requires unique | -> E0500 -> see "E0500" section. Fix family: drop the conflicting borrow before the closure or restructure capture.
| access to ... but it |
| is already borrowed" |
| |
| "temporary value dropped | -> E0716 -> see "E0716" section. Fix family: `let`-bind the temporary so it outlives the borrow.
| while borrowed" |
| |
+---------------------------+
ALWAYS map the message to the error code first. NEVER guess a fix from the line number alone.
E0382: use of moved value
Official summary (verbatim): "A variable was used after its contents have been moved elsewhere." Source: rustc_error_codes/E0382.md.
Root cause
A non-Copy value (String, Vec, Box, custom struct without derive(Copy), etc.) was moved by:
- assignment to another binding (
let y = x;) - pass to a function by value (
takes_ownership(x);) - pattern bind in
match/if letwithoutref/ref mut - being captured by-move in a closure
- being put into a container (
vec.push(x);)
After the move, the original binding is uninitialised. Using it triggers E0382.
Erroneous example
struct MyStruct { s: u32 }
fn main() {
let mut x = MyStruct { s: 5 };
let y = x; // moves x into y
x.s = 6; // E0382: use of moved value `x`
println!("{}", x.s);
}
Fix patterns (apply in this priority order)
Borrow instead of move. If the consumer only reads, take
&T. If it writes, take&mut T. ALWAYS try this first.fn calculate_length(s: &String) -> usize { s.len() } // borrow, not move let s1 = String::from("hello"); let n = calculate_length(&s1); println!("{s1} {n}"); // s1 still validRestructure ownership so the function takes ownership only when it really keeps the value. Return the value back if the caller needs it after.
fn process(s: String) -> String { /* mutate */ s } let mut s = String::from("hello"); s = process(s); // ownership returnedstd::mem::take(&mut x)for types implementingDefault. Leavesx = T::default()soxis still initialised.use std::mem; let mut v = vec![1, 2, 3]; let taken = mem::take(&mut v); // v is now empty Vec, taken owns [1,2,3]std::mem::replace(&mut x, new)swaps out the value, gives you the old one. Use when you must put a specific replacement, notDefault.use std::mem; let mut s = String::from("old"); let old = mem::replace(&mut s, String::from("new")); // s = "new", old = "old".clone()ONLY when (a) the data is small /Copy-ish, (b) you genuinely need two independent owners, and (c) you have already considered patterns 1 to 4. Document why a clone is correct, not a workaround.
NEVER add .clone() without first asking "can I borrow instead". .clone() on a String or Vec copies the heap allocation; clippy::redundant_clone will flag obvious abuses.
After-NLL improvement
The error is reported at the use site, after NLL (Rust 2018+). The compiler often suggests the exact line to clone or borrow. ALWAYS read the suggestion before guessing.
E0502: cannot borrow as mutable because also borrowed as immutable
Official summary (verbatim): "A variable already borrowed with a certain mutability (either mutable or immutable) was borrowed again with a different mutability." Source: rustc_error_codes/E0502.md.
Root cause
Rust's rule: at any point, either one &mut xor any number of & to the same value. Two overlapping borrows of different mutability violate this.
Erroneous example
fn bar(x: &mut i32) {}
fn foo(a: &mut i32) {
let y = &a; // immutable borrow of a
bar(a); // E0502: cannot borrow `*a` as mutable because also borrowed as immutable
println!("{y}");
}
Fix patterns
Tighten the shared borrow scope (NLL). Use the shared borrow, finish using it, then reborrow as
&mut. NLL ends a borrow at its last use, not at the closing brace.fn foo(a: &mut i32) { let y = &a; println!("{y}"); // last use of y, borrow ends here bar(a); // OK, now mutable borrow is fresh }Extract the read into a local so the
&borrow ends before the&mutbegins.let len = vec.len(); // borrow ends after `len` is bound (Copy) vec.push(len); // mutable borrow OKRestructure to avoid simultaneous borrow. Read first, write second; or write first, read second. Never both in flight.
Two-phase borrows already cover the common case
vec.push(vec.len())since Rust 1.18. If your code looks like this and still fails, the issue is more subtle (often E0499); see that section.
NEVER reach for Arc / RefCell to evade E0502. Those tools exist for genuine shared-ownership and interior-mutability problems, not for scope errors.
E0596: cannot borrow as mutable, as it is not declared as mutable
Official summary (verbatim): "This error occurs because you tried to mutably borrow a non-mutable variable." Source: rustc_error_codes/E0596.md.
Root cause
A binding declared without mut cannot be borrowed &mut. A method declared &self cannot mutate the receiver.
Erroneous examples
let x = 1;
let y = &mut x; // E0596: cannot borrow `x` as mutable
struct Counter { n: u32 }
impl Counter {
fn bump(&self) { self.n += 1; } // E0596: `self` is &, not &mut
}
Fix patterns
Add
mutto the binding.let mut x = 1; let y = &mut x; // OKChange
&selfto&mut selfon the method that mutates.impl Counter { fn bump(&mut self) { self.n += 1; } }If the receiver must stay
&self(e.g. trait method signature), use interior mutability:Cell<T>(forCopy),RefCell<T>(single-threaded, runtime-checked),Mutex<T>/RwLock<T>(thread-safe). See [[rust-syntax-borrowing]].use std::cell::Cell; struct Counter { n: Cell<u32> } impl Counter { fn bump(&self) { self.n.set(self.n.get() + 1); } }
NEVER promote a binding to Arc<Mutex<T>> just to fix E0596 in a single-threaded path. Use Cell or RefCell, or add mut. Over-engineering hides intent.
E0499: cannot borrow as mutable more than once at a time
Official summary (verbatim): "A variable was borrowed as mutable more than once." Source: rustc_error_codes/E0499.md.
Root cause
Two &mut references to the same value overlap in time. Rust forbids this because mutable aliasing is unsound.
Erroneous example
let mut i = 0;
let x = &mut i;
let a = &mut i; // E0499: cannot borrow `i` as mutable more than once
x;
Fix patterns
Scope-tighten via NLL. Finish using the first
&mutbefore creating the second. Many naive cases vanish.let mut i = 0; { let x = &mut i; *x += 1; } // first borrow ends here let a = &mut i; // OKSplit borrow across disjoint struct fields. The compiler tracks paths;
&mut s.aand&mut s.bare allowed whenaandbare distinct fields.struct S { a: u32, b: u32 } let mut s = S { a: 0, b: 0 }; let pa = &mut s.a; let pb = &mut s.b; // OK, disjoint paths *pa = 1; *pb = 2;Split borrow on a slice via
split_at_mutfor two disjoint halves.let mut v = vec![1, 2, 3, 4]; let (left, right) = v.split_at_mut(2); left[0] = 10; right[0] = 30;Restructure ownership tree. If the same
&mutneeds to flow into two consumers, hand it to one, finish, hand to the next. Sequence the work.
NEVER hold the first &mut longer than necessary to extend its lifetime past the conflict; that is the opposite direction. The fix is shorter borrows, not longer.
E0500: closure requires unique access but it is already borrowed
Official summary (verbatim): "A borrowed variable was used by a closure." Source: rustc_error_codes/E0500.md.
Root cause
A closure captures a value by unique (&mut) reference, but the same value is already borrowed (shared or mutable) elsewhere in scope.
Erroneous example
fn you_know_nothing(jon_snow: &mut i32) {
let nights_watch = &jon_snow; // shared borrow
let starks = || { *jon_snow = 3; }; // E0500: closure wants unique access
println!("{nights_watch}");
}
Fix patterns
Drop the conflicting borrow before the closure is created (or first called).
fn you_know_nothing(jon_snow: &mut i32) { let nights_watch = &jon_snow; println!("{nights_watch}"); // borrow ends let starks = || { *jon_snow = 3; }; starks(); }movekeyword to force capture by value. Use when the closure should own the captured data and no outer borrow is needed.let v = vec![1, 2, 3]; let print_v = move || println!("{v:?}");movedoes not fix overlapping borrow of the outer variable; it sidesteps the problem by transferring ownership.Restructure capture set. Capture only the field you need, not the whole struct. Closures see the path you actually use.
struct S { a: i32, b: i32 } let mut s = S { a: 0, b: 0 }; let r_b = &s.b; // disjoint capture let mut f = || s.a += 1; // OK, captures only s.a f(); println!("{r_b}");Clone for genuinely independent users. If a closure must run later with a snapshot of the value, clone before capturing.
NEVER pass &mut to a closure and then read the outer variable while the closure is live. The closure holds the unique reference until dropped.
E0716: temporary value dropped while borrowed
Official summary (verbatim): "A temporary value is being dropped while a borrow is still in active use." Source: rustc_error_codes/E0716.md.
Root cause
&expr where expr is a function call, arithmetic result, or other rvalue creates an anonymous temporary. The temporary lives only until the end of the enclosing statement (the rules are in the Reference under "Temporary lifetimes"). If a borrow of that temporary tries to outlive the statement, E0716 fires.
Erroneous example
fn foo() -> i32 { 22 }
fn bar(x: &i32) -> &i32 { x }
let p = bar(&foo()); // &foo() creates a temporary that dies after this statement
let q = *p; // E0716: temporary dropped while borrowed
Fix patterns
let-bind the temporary to extend its lifetime to the end of the enclosing block.let value = foo(); // owns the i32 for the whole block let p = bar(&value); let q = *p; // OKlet tmp = &expr;also extends the temporary's lifetime to the enclosing block via temporary-lifetime-extension rules.let p = &foo(); // temporary extended to block scope let q = *p;Restructure so the call result is consumed within the same statement if you do not need it later.
println!("{}", bar(&foo())); // single statement, no later use of p
NEVER Box::leak(Box::new(expr)) to silence E0716. That leaks memory permanently. Use let-binding.
Cross-cutting fix patterns
These apply to multiple error codes. Try in this order when the specific section above does not match:
| Pattern | Use when |
|---|---|
| Borrow instead of move | E0382 root pattern. Function reads or writes but does not consume. |
| Introduce intermediate binding | E0502 + E0716. Extracts a value out of a borrow so the borrow can end. |
| Scope-tighten via NLL | E0502 + E0499 + E0500. The borrow ends at last use; restructure so the second access is past that point. |
Split borrow (struct fields or split_at_mut) |
E0499 across disjoint paths. |
mem::take / mem::replace |
E0382 when you need ownership but cannot move. |
Interior mutability (Cell / RefCell) |
E0596 when receiver must stay &self. |
move closure |
E0500 when closure should own the data. |
let-bind to extend temporary |
E0716 verbatim fix. |
.clone() |
Last resort. ONLY when the alternatives fail or the value is small / cheap. |
Anti-patterns (never do this)
See references/anti-patterns.md for full WHY explanations. Headlines:
- Cloning everything to silence E0382. Doubles heap cost. Hides the real ownership question.
RefCellto bypass E0502 / E0499. Pushes a compile-time error into a runtime panic.Arc<Mutex<T>>to fix E0596 in single-threaded code. Adds atomic cost for zero benefit.Box::leakto extend a temporary in E0716. Permanent memory leak.unsafe+ raw pointer to dodge the borrow checker. Undefined behavior risk; soundness now on you.- Holding a
MutexGuardpast its needed scope to silence E0499. Extends critical section, hurts contention.
Reference files
references/methods.md: complete fix-recipe catalog with API signatures (mem::take,mem::replace,split_at_mut,Cell,RefCell, two-phase borrow rules, NLL spec).references/examples.md: side-by-side broken-vs-fixed for every error code, plus realistic Vec / HashMap / struct patterns.references/anti-patterns.md: each anti-pattern enumerated with broken code, why it compiles, why it harms, and the correct alternative.
Sources
- Rust Compiler Error Index: https://doc.rust-lang.org/error_codes/error-index.html
- E0382: https://doc.rust-lang.org/error_codes/E0382.html
- E0502: https://doc.rust-lang.org/error_codes/E0502.html
- E0596: https://doc.rust-lang.org/error_codes/E0596.html
- E0499: https://doc.rust-lang.org/error_codes/E0499.html
- E0500: https://doc.rust-lang.org/error_codes/E0500.html
- E0716: https://doc.rust-lang.org/error_codes/E0716.html
- The Rust Book, Ch. 4 References and Borrowing: https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html
std::mem: https://doc.rust-lang.org/std/mem/index.htmlstd::cell: https://doc.rust-lang.org/std/cell/index.html