Introduction
Error E0502 is one of the most common borrow checker errors in Rust. It occurs when your code attempts to mutably borrow a value while an immutable borrow to that same value is still active. The Rust compiler prevents this to guarantee memory safety without a garbage collector.
Symptoms
- Compiler error
E0502: cannot borrow as mutable because it is also borrowed as immutable - Error points to two lines: the immutable borrow and the mutable borrow
- Common in loops that read from and write to the same collection
- Occurs when a reference is held across a function call that needs mutation
- Error persists even after narrowing scope in older Rust editions
Common Causes
- Holding a reference to a collection element while modifying the collection
- Iterator holds an immutable borrow; calling a mutating method on the same data
- Struct field reference held while mutating another field of the same struct
- Return value holds a reference that outlives the intended scope
Step-by-Step Fix
- 1.Limit the scope of the immutable borrow:
- 2.```rust
- 3.// Before: borrow spans the entire function
- 4.let max_score = scores.iter().max().unwrap();
- 5.scores.push(100); // Error!
- 6.println!("Max was: {}", max_score);
// After: immutable borrow ends before mutable borrow let max_score = scores.iter().max().copied().unwrap_or(0); scores.push(100); // OK: max_score is a copy, not a reference println!("Max was: {}", max_score); ```
- 1.Use indices instead of references in loops:
- 2.```rust
- 3.// Before: holds references while mutating
- 4.for item in items.iter() {
- 5.if item.should_duplicate() {
- 6.items.push(item.clone()); // Error!
- 7.}
- 8.}
// After: collect first, then mutate let to_add: Vec<_> = items.iter() .filter(|item| item.should_duplicate()) .cloned() .collect(); items.extend(to_add); // OK: immutable borrow ended ```
- 1.Use separate variables to avoid overlapping borrows:
- 2.```rust
- 3.// Before
- 4.let name = &user.name;
- 5.user.update_name("new"); // Error: mutable borrow while immutable active
- 6.println!("Old name: {}", name);
// After: extract the value, don't hold a reference let name = user.name.clone(); user.update_name("new"); // OK println!("Old name: {}", name); ```
- 1.**Use
RefCellfor runtime borrow checking when needed**: - 2.```rust
- 3.use std::cell::RefCell;
struct Cache { data: RefCell<Vec<String>>, }
impl Cache { fn add_and_read(&self) { // Runtime borrow checking instead of compile-time self.data.borrow_mut().push("new item".to_string()); let len = self.data.borrow().len(); println!("Cache size: {}", len); } } ```
Prevention
- Prefer
.copied()or.cloned()to convert references to owned values - Use
split_at_mut()orsplit_first_mut()for disjoint mutable access - Restructure code to minimize the lifetime of references
- Use indices (
for i in 0..vec.len()) instead of iterators when mutation is needed - Consider whether the data structure can be redesigned to avoid shared mutation
- Use
cellmodule types (Cell,RefCell) for interior mutability when unavoidable