Rust: error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable

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 for loop? That's the most common case — collect into a second Vec, or use retain/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:

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":

✗ Rejected — the two borrow ranges overlap let first = &v[0]; v.push(4); println!("{}", first) shared borrow of v … still live here &mut v both borrows are live in this column → E0502 ✓ Accepted — the last use moves before the mutation let first = &v[0]; println!("{}", first) v.push(4); shared borrow ends here &mut v no overlap same lines, same scope — only the order changed

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

ErrorWhat conflictsTypical fix
E0502 (this page)A &mut overlapping a shared &End the shared borrow first
E0499Two &mut borrows at onceShorten one range, or split the borrows
E0597A reference outliving the value it points atExtend the value's scope, or return owned data
E0382Using a value after it was movedBorrow 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

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.

All Error References Rust E0499: two mutable borrows Rust E0597: does not live long enough
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.