Quick answer
A shared borrow is still alive when you take a &mut. End the shared borrow first:
- Move the reference's last use above the mutating line — usually the whole fix.
- Pushing inside a
forloop? That's the most common case — collect into a secondVec, or useretain/iter_mut. - Copy the value out (
let n = v[0];) so no reference is held at all. - Inside a method? Borrow the individual struct fields instead of all of
self.
The exact error string
fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0];
v.push(4);
println!("{}", first);
}
// error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
// --> src/main.rs:4:5
// |
// 3 | let first = &v[0];
// | - immutable borrow occurs here
// 4 | v.push(4);
// | ^^^^^^^^^ mutable borrow occurs here
// 5 | println!("{}", first);
// | ----- immutable borrow later used here
Rust gives you three annotations, and the third is the one people skip. immutable borrow occurs here is where the shared borrow starts, mutable borrow occurs here is the line that failed — but immutable borrow later used here is the reason it failed. That last line is what keeps the shared borrow alive across your mutation. Find it, and you've found the thing to move or delete.
When you'll see this error
Almost every real E0502 is one of four situations. Find yours, then jump to its fix:
- Calling
push()(or any&mutmethod) while iterating aVec— by far the most common → Fix 2. - Reading from a collection and modifying it before the read's borrow has ended → Fix 1 or Fix 3.
- Iterating
&self.fieldwhile calling another&mut selfmethod on the same struct → Fix 4. - Simply holding a reference longer than necessary, so it's still live at the mutation → Fix 1.
Two things worth knowing up front. First, since non-lexical lifetimes (NLL), a borrow ends at its last use rather than at the end of the enclosing scope — which is why moving a single line often fixes this outright. Second, Rust isn't being pedantic here: if Vec::push reallocates the backing buffer, any existing reference into it would point at freed memory, so the borrow checker is preventing a genuine use-after-free at compile time.
Not your error? E0499 is two mutable borrows, E0382 is a value that was moved, and E0597 is a reference outliving its value — there's a full comparison table further down.
The mechanism: overlapping borrow ranges
Rust's rule is that at any single point in the program, a value can have either one mutable borrow or any number of shared borrows — never both at once. Since non-lexical lifetimes (stabilized in Rust 1.31 for the 2018 edition, and extended to 2015-edition crates in 1.36), a borrow's live range ends at its last use, not at the closing brace of its block. So the question is never "is the reference still in scope" but "do the two ranges overlap":
The borrow checker compares live ranges, not scopes. Nothing was cloned or restructured between these two versions — the shared borrow simply stops being used before the mutable one begins.
Fix 1: move the last use before the mutation
This is the diagram above, in code. If you only need the borrowed value before the mutation, say so by using it there:
let mut v = vec![1, 2, 3];
// ❌ rejected — the borrow is still needed after the mutation
// let first = &v[0];
// v.push(4);
// println!("{}", first);
// ✅ accepted — identical lines, only the order changed
let first = &v[0];
println!("{}", first); // last use of the shared borrow — it ends here
v.push(4); // nothing is borrowed now
Read those two blocks against each other: same three statements, same scope, same variables. Only the ordering changed — no clone, no restructuring, and nothing extra at runtime. This is the fix a surprising share of E0502s actually want, which is why it's worth trying before anything more involved.
Fix 2: don't mutate a collection while iterating it
This is the most common real-world shape of E0502 by a wide margin — if you arrived here from a push inside a for loop, this is your section. for x in &v holds a shared borrow for the entire loop body:
let mut v = vec![1, 2, 3];
// ❌ for x in &v {
// ❌ v.push(*x * 2);
// ❌ }
// error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
// --> src/main.rs:4:9
// |
// 3 | for x in &v {
// | --
// | |
// | immutable borrow occurs here
// | immutable borrow later used here
// 4 | v.push(*x * 2);
// | ^^^^^^^^^^^^^^ mutable borrow occurs here
This one isn't the borrow checker being fussy — it's preventing a genuine use-after-free. push can reallocate the vector's backing buffer when it outgrows its capacity, which would leave the iterator walking freed memory. Languages without this check hand you a crash or silent corruption instead. Pick whichever fix matches your intent:
// ✅ collect first, mutate after — the shared borrow ends with the loop
let additions: Vec<i32> = v.iter().map(|x| x * 2).collect();
v.extend(additions);
// ✅ removing instead of adding? retain does it in one pass, no second borrow
v.retain(|x| *x % 2 == 0);
// ✅ modifying in place? iterate mutably — one &mut, no shared borrow at all
for x in &mut v {
*x *= 2;
}
// ✅ need to add while inspecting? loop by index, so nothing stays borrowed
let original_len = v.len();
for i in 0..original_len {
let doubled = v[i] * 2; // borrow ends at the end of this statement
v.push(doubled);
}
Fix 3: copy the value out instead of borrowing it
If you genuinely need the value after the mutation, don't hold a reference across it. For Copy types this is free:
let mut v = vec![1, 2, 3];
let first = v[0]; // ✅ i32 is Copy — this is a value, not a borrow
v.push(4);
println!("{}", first); // ✅ fine — `first` never borrowed v
For non-Copy types such as String, .clone() does the same job at the cost of an allocation. That's a real cost, so prefer Fix 1 when ordering allows it — but a clone is far better than reaching for unsafe or restructuring the whole function around the borrow checker.
Fix 4: split borrows by struct field
Inside a method, calling self.something_mut() while iterating &self.items borrows all of self twice, even though the two operations touch different fields:
struct App {
items: Vec<String>,
processed: usize,
}
impl App {
fn bump(&mut self) {
self.processed += 1;
}
fn run(&mut self) {
// ❌ for item in &self.items {
// ❌ println!("{}", item);
// ❌ self.bump();
// ❌ }
// error[E0502]: cannot borrow `*self` as mutable because it is also
// borrowed as immutable
}
}
The borrow checker can see that two direct field accesses are disjoint — &self.items and &mut self.processed coexist happily. What it can't do is look inside a method call: self.bump() takes &mut self as a whole, so all field-level precision is lost. Work at the field level instead:
impl App {
fn run(&mut self) {
// ✅ touch the fields directly — disjoint borrows are allowed
for item in &self.items {
println!("{}", item);
self.processed += 1;
}
}
}
// ✅ or, when the logic is worth keeping in a function, pass only what it needs
fn bump(processed: &mut usize) {
*processed += 1;
}
impl App {
fn run_split(&mut self) {
let App { items, processed } = self; // destructure into per-field borrows
for item in items.iter() {
println!("{}", item);
bump(processed);
}
}
}
It's worth being explicit about why those two versions differ, because it looks arbitrary at first: iterating &self.items while touching self.processed looks like a whole-self conflict, but it compiles — the checker resolves each field access individually. Swap that line for self.bump() and the identical logic is rejected, because a method signature says &mut self and the checker can't see which fields the body actually touches. The method call is what breaks it, not the field access.
The destructuring form in run_split is the general escape hatch, and the one to reach for when you do want the logic in a function: it converts one &mut self into independent borrows of each field, which the checker then tracks separately. For slices specifically, split_at_mut does the same job for two disjoint ranges of one collection.
Fix 5: interior mutability, knowingly
RefCell<T> (single-threaded) and Mutex<T> (across threads) let you mutate through a shared reference. This does not remove the aliasing rule — it moves enforcement from compile time to run time:
use std::cell::RefCell;
let v = RefCell::new(vec![1, 2, 3]);
v.borrow_mut().push(4); // ✅ compiles
// ⚠️ the same conflict now panics at RUNTIME instead of failing to compile:
// let borrowed = v.borrow(); // shared borrow held...
// v.borrow_mut().push(5); // → panics: already borrowed: BorrowMutError
// println!("{:?}", borrowed);
Use it when the borrow pattern is genuinely dynamic — graph structures, shared observers, a cache behind an Rc — not to silence a conflict that reordering (Fix 1) or a copy (Fix 3) would resolve with no runtime cost and no panic risk.
The borrow-checker error family
| Error | What conflicts | Typical fix |
|---|---|---|
| E0502 (this page) | A &mut overlapping a shared & | End the shared borrow first |
| E0499 | Two &mut borrows at once | Shorten one range, or split the borrows |
| E0597 | A reference outliving the value it points at | Extend the value's scope, or return owned data |
| E0382 | Using a value after it was moved | Borrow instead of move, or clone |
All four are the same checker reporting different shapes of the same question: for every reference, is its live range compatible with everything else happening to that value? Reading the "later used here" annotation is the shared skill across all of them.
Debugging checklist
- ✓ Find the
immutable borrow later used hereannotation — that line is why the borrow is still alive - ✓ Can that use move above the mutating line? That's the zero-cost fix
- ✓ Is the value
Copy? Bind by value (v[0]) instead of by reference (&v[0]) - ✓ Mutating inside a
for ... in &collectionloop? Collect first and mutate after, or useretain/drain/iter_mut - ✓ Inside an
impl? Replaceself.method()with direct field access, or destructureselfinto per-field borrows - ✓ Need two disjoint parts of one slice?
split_at_mutgives you both safely - ✓ Reaching for
RefCell? Confirm the pattern is genuinely dynamic — otherwise you're trading a compile error for a runtime panic - ✓ On an old toolchain? Non-lexical lifetimes need Rust 1.31+ (2018 edition) or 1.36+ (2015 edition); before that, borrows ran to the end of the block
Frequently Asked Questions
What does error[E0502]: cannot borrow as mutable because it is also borrowed as immutable mean?
At the moment you took a mutable borrow, a shared (immutable) borrow of the same value was still live. Rust's core aliasing rule allows either one mutable borrow or any number of shared borrows at a time, never both. The compiler's three annotations tell you the whole story: where the immutable borrow starts, where the mutable borrow happens, and — most importantly — where the immutable borrow is later used, which is what keeps it alive across the mutation.
How do I fix E0502?
End the shared borrow before the mutation. In practice that means one of: move the last use of the reference above the mutating call so non-lexical lifetimes end the borrow early; copy or clone the value out so no reference is held at all; restructure a mutate-while-iterating loop to collect indices or use retain/drain; or borrow individual struct fields instead of all of self.
Why does the error point at a line after my mutable borrow?
Because that later line is the reason the shared borrow is still alive. Since non-lexical lifetimes (Rust 1.31, 2018 edition), a borrow ends at its last use rather than at the end of the enclosing block. The immutable borrow later used here annotation is pointing at the exact line that extends the borrow past your mutation — delete or move that use and the error usually disappears.
Why can't I push to a vector while iterating over it?
Iterating with for x in &v holds a shared borrow of the vector for the whole loop, and push needs a mutable borrow. Beyond the borrow rule, this is a genuine memory-safety issue: pushing can reallocate the vector's backing buffer, which would leave the iterator pointing at freed memory. Collect what you want to add into a separate Vec and extend after the loop, or iterate by index, or use retain for removals.
What is the difference between E0499 and E0502?
E0499 is two mutable borrows alive at once. E0502 is a mutable borrow overlapping a shared (immutable) borrow. Both enforce the same underlying rule — a mutable borrow must be exclusive — they just describe different combinations, and the fixes are the same family: shorten one borrow's live range so the two no longer overlap.
Should I use RefCell to get around E0502?
Only as a last resort, and knowingly. RefCell doesn't remove the aliasing rule — it moves the check from compile time to run time, so the same conflicting borrows panic with "already borrowed: BorrowMutError" instead of failing to compile. Reach for it when the borrow pattern is genuinely dynamic (graph structures, shared observers), not to silence a conflict that reordering or copying would fix safely.
More Rust & backend errors
Browse the full reference for Rust, Go, and Java errors — exact message, cause, and fix.