Common Borrowing Errors
When the borrow checker detects an invalid borrow, it reports a specific E0xxx diagnostic pointing to the creation of the first borrow, the conflicting operation, and the later use that kept the first borrow alive.
Complete Borrowing Error Reference Table
| Error Code | Diagnostic Message | Cause | Standard Fix |
|---|---|---|---|
E0502 |
cannot borrow 'x' as mutable because it is also borrowed as immutable (or vice versa) |
Overlapping &x and &mut x lifetimes |
Finish reading &x before taking &mut x, copy/clone needed values upfront, or split struct fields |
E0499 |
cannot borrow 'x' as mutable more than once at a time |
Two overlapping &mut x borrows |
Sequence the &mut borrows one after another, or use .split_at_mut(mid) on slices |
E0596 |
cannot borrow 'x' as mutable, as it is not declared as mutable |
Taking &mut x when x was declared let x |
Change declaration to let mut x |
E0594 |
cannot assign to '*r', which is behind a '&' reference |
Writing through &T |
Change parameter or variable type to &mut T |
E0505 |
cannot move out of 'x' because it is borrowed |
Moving ownership of x while &x is still active |
Finish using &x before moving x |
E0506 |
cannot assign to 'x' because it is borrowed |
Overwriting x = new_val while &x is still active |
Finish using &x before reassigning x |
E0507 |
cannot move out of '*r' which is behind a shared/mutable reference |
Moving a non-Copy field/element out of &T or &mut T |
Borrow &r.field, .clone() it, or use std::mem::take / replace on &mut |
E0515 |
cannot return value referencing local variable |
Returning &local from a function |
Return an owned value (String, Vec<T>) instead of a reference |
E0597 |
'x' does not live long enough |
Storing &x into a reference that outlives x's block |
Move x to the outer block or shorten the reference's scope |
E0716 |
temporary value dropped while borrowed |
Borrowing a temporary (&String::from("a").trim()) across statements |
Bind the temporary to a named let variable first |
Walkthrough of Three Classic Borrowing Pitfalls and Fixes
1. Mutating a Collection While Holding a Reference Into It (E0502)
// ❌ Problem:
// let first = &list[0];
// list.push(4);
// println!("{first}");
fn main() {
let mut list = vec![10, 20, 30];
// ✅ Fix 1 (for Copy elements): Dereference/copy the value (`list[0]`) instead of borrowing `&list[0]`!
let first: i32 = list[0];
list.push(40);
println!("first = {first}, list = {list:?}");
}
first = 10, list = [10, 20, 30, 40]
2. Accumulating Mutable References Inside a Loop (E0499)
If a helper function takes &'a mut State and returns a reference tied to that same lifetime &'a T that you store outside a loop, the &mut State borrow stays locked across loop iterations (E0499).
Fix: Ensure methods that only need to read State after mutating it are separated, or return owned/copied values (such as indices or cloned values) across loop iterations rather than holding long-lived borrows into &mut State.
3. Moving Out of a Borrowed Struct Field (E0507) via std::mem::take
When implementing a state transition or draining method on &mut self where you want to take ownership of a String or Vec<T> field:
struct JobQueue {
pending: Vec<String>,
}
impl JobQueue {
fn drain_all(&mut self) -> Vec<String> {
// Moving `self.pending` directly would fail with `E0507`!
// `std::mem::take` swaps in an empty `Vec::new()` and returns the owned `Vec<String>`:
std::mem::take(&mut self.pending)
}
}
fn main() {
let mut queue = JobQueue {
pending: vec![String::from("job_1"), String::from("job_2")],
};
let jobs = queue.drain_all();
println!("Drained: {jobs:?}, remaining in queue: {:?}", queue.pending);
}
Drained: ["job_1", "job_2"], remaining in queue: []