Rust Error E0502: Cannot Borrow as Mutable Because It Is Also Borrowed as Immutable
E0502 means you tried to change a value while some other part of your code was still reading it through a reference. It shows up all the time with vectors, and the fix is usually to move one line or copy one value. This page gives you the fix first, then explains why Rust insists on it. For the other errors in this series, see Rust compiler errors.
The error message
Section titled “The error message”error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable --> src/main.rs:4:5 |3 | let first = &v[0]; // a reference into the Vec | - immutable borrow occurs here4 | v.push(4); // ❌ changes the Vec while `first` is alive | ^^^^^^^^^ mutable borrow occurs here5 | println!("First: {first}"); // ...because `first` is used here | ----- immutable borrow later used hereThis is the program that produces it:
fn main() { let mut v = vec![1, 2, 3]; let first = &v[0]; // a reference into the Vec v.push(4); // ❌ changes the Vec while `first` is alive println!("First: {first}"); // ...because `first` is used here}What it means
Section titled “What it means”The compiler points at three lines, and you need all three to understand the error:
- Line 3:
&v[0]creates a read-only reference intov. That’s the immutable borrow. - Line 4:
v.push(4)needs to changev, so it takes a mutable borrow. - Line 5:
firstis used again. This is the line that keeps the first borrow alive across line 4.
Without line 5, the code would compile. The problem is not that both borrows exist; it’s that they overlap.
Why Rust does this
Section titled “Why Rust does this”The rule is: many readers or one writer, never both at the same time. You learned it on the Borrowing page.
For a Vec there’s a very concrete reason. A Vec keeps its items in one block of memory. When you push and that block is full, the Vec moves everything to a new, bigger block and frees the old one. Any reference into the old block (like first) now points at freed memory. In C++ this is the famous “iterator invalidation” bug, and it can crash your program or quietly read garbage.
It’s like writing down a friend’s address, and then they move to a bigger house. Your note still looks fine, but it points to an empty building. Rust refuses to compile the code instead of letting you visit the empty building.
How to fix it
Section titled “How to fix it”Fix 1: Finish reading before you write
Section titled “Fix 1: Finish reading before you write”Choose this first. Since Rust 2018, a borrow ends at its last use, not at the closing brace. This is called non-lexical lifetimes. So just finish using the reference before you change the vector:
fn main() { let mut v = vec![1, 2, 3]; let first = &v[0]; println!("First: {first}"); // last use of `first`: the borrow ends here v.push(4); // ✅ nobody is reading anymore println!("Now: {:?}", v);}Output:
First: 1Now: [1, 2, 3, 4]Fix 2: Copy the value out
Section titled “Fix 2: Copy the value out”Choose this when you need the old value after the change. Take a copy instead of a reference. For numbers, just drop the &. For a String, use .clone():
fn main() { let mut queue = vec![String::from("deploy"), String::from("test")]; let next = queue[0].clone(); // our own copy, not a reference queue.push(String::from("release")); println!("Next up: {next}"); println!("Queue: {:?}", queue);}Output:
Next up: deployQueue: ["deploy", "test", "release"]With let first = v[0]; on a Vec<i32>, you get a copy for free, because i32 is Copy.
Fix 3: Use iter_mut or retain to change items in place
Section titled “Fix 3: Use iter_mut or retain to change items in place”Choose this when you wrote a loop like for p in prices.iter() and then tried prices.remove(i) or changed items inside it. That also gives E0502. The standard library has methods made for exactly this:
fn main() { let mut prices = vec![120, 45, 300, 80];
// change every item in place for p in prices.iter_mut() { *p = *p * 9 / 10; // 10% off }
// remove the items you don't want prices.retain(|&p| p >= 50);
println!("{:?}", prices);}Output:
[108, 270, 72]iter_mut gives you one &mut per item, so there is only ever one writer. retain does the removing for you, safely, in one pass. See Vectors and Iterators.
Fix 4: Collect the changes, then apply them
Section titled “Fix 4: Collect the changes, then apply them”Choose this when you need to add items based on what you read. First read and collect, then write in a separate step:
fn main() { let mut scores = vec![10, 25, 7, 40]; let bonuses: Vec<i32> = scores.iter().filter(|&&s| s > 20).map(|s| s * 2).collect(); scores.extend(bonuses); // the read borrow is over, so we can write println!("{:?}", scores);}Output:
[10, 25, 7, 40, 50, 80]The extra Vec costs a little memory, but the code is clear and obviously correct. That’s usually a good trade.
Fix 5: Use indices
Section titled “Fix 5: Use indices”Choose this as a last resort, when the other fixes don’t fit. An index is just a number, not a borrow, so it doesn’t block changes:
fn main() { let mut scores = vec![10, 25, 7]; for i in 0..scores.len() { if scores[i] > 20 { scores.push(scores[i] * 2); } } println!("{:?}", scores);}Output:
[10, 25, 7, 50]0..scores.len() is worked out once, before the loop starts, so the new items are not visited. Be careful when you remove items by index: the positions shift and you can skip items or panic. For removing, retain (Fix 3) is safer.
Where you’ll usually see it
Section titled “Where you’ll usually see it”Pushing to a Vec while looping over it. for s in &scores { scores.push(..) } borrows scores for the whole loop. Fix: collect first and extend afterward (Fix 4), or loop by index (Fix 5).
Calling a &mut self method while looping over a field. This is very common inside impl blocks:
fn audit(&mut self) { for item in &self.items { self.record(item); // ❌ cannot borrow `*self` as mutable because it is also borrowed as immutable }}self.record borrows all of self mutably, including self.items. Fix: touch the other field directly (self.log.push(item.clone())), because Rust allows borrowing two different fields at once. Or clone the items first. See Structs.
Keeping a value from a HashMap while inserting. let apples = stock.get("apples").unwrap(); then stock.insert(..) then using apples gives E0502. Fix: copy the value out with let apples = *stock.get("apples").unwrap(); or .copied(). See HashMaps.
Related errors
Section titled “Related errors”- E0499: two mutable borrows at the same time. The same idea, with two writers instead of a reader and a writer.
- E0382: you moved a value and then used it. Often appears when you fix E0502 by moving instead of borrowing.
- E0106: a missing lifetime specifier on a function that returns a reference.
- All compiler errors: the full list of error pages.
Quick check
Section titled “Quick check”Quick check
1. Why can let first = &v[0]; v.push(4); println!("{first}"); not compile?
2. You want to remove every item under 50 from a Vec<i32>. What is the cleanest fix for E0502?
3. When does an immutable borrow end in modern Rust?
Score: 0 / 3
Read more
Section titled “Read more”- E0502 — Rust error codes index: the official explanation of this error.
- Storing Lists of Values with Vectors — The Rust Book: includes this exact
let first = &v[0]; v.push(..)example and why it fails. - References and Borrowing — The Rust Book: the “many readers or one writer” rule in full.
- Non-lexical lifetimes — The Edition Guide: why a borrow now ends at its last use instead of at the closing brace.
Vec— standard library docs:retain,iter_mut,extend, and the other methods that let you change a vector safely.