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
- Copy the value out instead of holding a reference:
let first = scores[0];(works forCopytypes like numbers), or.clone()a smallString. - Reorder: finish using the reference before mutating. Borrows end at their last use, so moving the
println!above thepushis enough. - Collect first, then modify: loop to gather what to add (
let extra: Vec<_> = ...), thenqueue.extend(extra)after the loop. - Use indices when you must modify during a loop:
for i in 0..queue.len() { ... queue[i] ... }. Indexing borrows only for that one expression. - Use the method made for it:
retainto remove while scanning,iter_mutto change elements in place,entryfor maps,sort_by_keyinstead 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(©);.
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(©);
println!("{chant}");
}
Output:
first is 10, all [10, 20, 30, 40]
queue [1, 2, 3, 200]
stock [5, 10, 2]
["ok", "fine"]
go go