RustArena

Rust error E0502: cannot borrow as mutable because it is also borrowed as immutable

Why Rust reports E0502 when you modify a Vec or String while a reference to it is alive, and five ways to fix it.

E0502 is the borrow checker enforcing "many readers or one writer": you tried to take a mutable borrow (&mut, often implicitly through a method like push) while a shared borrow (&) of the same value is still going to be used. Or the reverse: a shared borrow while a mutable one is alive.

The classic: a reference into a Vec, then push

fn main() {
    let mut scores = vec![10, 20, 30];
    let first = &scores[0];
    scores.push(40);
    println!("first is {first}");
}
error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let first = &scores[0];
  |                  ------ immutable borrow occurs here
4 |     scores.push(40);
  |     ^^^^^^^^^^^^^^^ mutable borrow occurs here
5 |     println!("first is {first}");
  |                         ----- immutable borrow later used here

This isn't the compiler being fussy. If push finds the vector full, it allocates a bigger heap block, moves the elements there, and frees the old block. first would then point at freed memory. In C++ this is the infamous iterator invalidation bug; in Rust it doesn't compile.

Read the three labels: where the shared borrow starts, where the conflicting mutable borrow happens, and where the shared borrow is later used. The fix is always to make those ranges stop overlapping.

Modifying a collection while looping over it

fn main() {
    let mut queue = vec![1, 2, 3];
    for job in &queue {
        if *job == 2 {
            queue.push(4);
        }
    }
}
error[E0502]: cannot borrow `queue` as mutable because it is also borrowed as immutable

The loop holds a borrow of queue for its whole duration.

The fixes

  1. Copy the value out instead of holding a reference: let first = scores[0]; (works for Copy types like numbers), or .clone() a small String.
  2. Reorder: finish using the reference before mutating. Borrows end at their last use, so moving the println! above the push is enough.
  3. Collect first, then modify: loop to gather what to add (let extra: Vec<_> = ...), then queue.extend(extra) after the loop.
  4. Use indices when you must modify during a loop: for i in 0..queue.len() { ... queue[i] ... }. Indexing borrows only for that one expression.
  5. Use the method made for it: retain to remove while scanning, iter_mut to change elements in place, entry for maps, sort_by_key instead of reading and writing by hand.

In methods: borrowing self twice

A common variant at work: inside a &mut self method, you loop over self.items and call another &mut self method in the loop body. The call borrows all of self mutably while self.items is borrowed. Fixes: access the other field directly (Rust tracks borrows of separate fields separately), make the helper a free function that takes only the fields it needs, or collect results first and apply them after the loop.

The reverse wording

s.push_str(&s) gives "cannot borrow s as immutable because it is also borrowed as mutable": push_str needs &mut s while its argument borrows s. Clone first: let copy = s.clone(); s.push_str(&copy);.

Example

fn main() {
    // Fix 1: copy the value out instead of holding a reference.
    let mut scores = vec![10, 20, 30];
    let first = scores[0];
    scores.push(40);
    println!("first is {first}, all {scores:?}");

    // Fix 3: collect the changes, then apply them after the loop.
    let mut queue = vec![1, 2, 3];
    let mut extra = Vec::new();
    for job in &queue {
        if *job % 2 == 0 {
            extra.push(job * 100);
        }
    }
    queue.extend(extra);
    println!("queue {queue:?}");

    // Fix 4: indices borrow only for one expression.
    let mut stock = vec![5, 0, 2];
    for i in 0..stock.len() {
        if stock[i] == 0 {
            stock[i] = 10;
        }
    }
    println!("stock {stock:?}");

    // Fix 5: retain removes while scanning.
    let mut names = vec!["ok", "spam", "fine", "spam"];
    names.retain(|n| *n != "spam");
    println!("{names:?}");

    // The reverse case: clone before pushing a string onto itself.
    let mut chant = String::from("go ");
    let copy = chant.clone();
    chant.push_str(&copy);
    println!("{chant}");
}

Output:

first is 10, all [10, 20, 30, 40]
queue [1, 2, 3, 200]
stock [5, 10, 2]
["ok", "fine"]
go go 

Practice it