Fix: Rust cannot borrow as mutable because it is also borrowed as immutable
Quick Answer
How to fix Rust cannot borrow as mutable error caused by aliasing rules, simultaneous references, iterator invalidation, struct method conflicts, and lifetime issues.
The Error
You compile Rust code and get:
error[E0502]: cannot borrow `data` as mutable because it is also borrowed as immutable
--> src/main.rs:5:5
|
4 | let r = &data[0];
| ---- immutable borrow occurs here
5 | data.push(4);
| ^^^^^^^^^^^^ mutable borrow occurs here
6 | println!("{}", r);
| - immutable borrow later used hereOr variations:
error[E0499]: cannot borrow `data` as mutable more than once at a timeerror[E0502]: cannot borrow `self.field` as mutable because it is also borrowed as immutableRust’s borrow checker prevents you from holding both a mutable and an immutable reference to the same data at the same time, or holding two mutable references simultaneously.
Why This Happens
Rust enforces ownership rules at compile time to prevent data races and memory safety issues. The core rule is:
- You can have either one mutable reference (
&mut) or any number of immutable references (&) to a value, but not both at the same time.
This prevents:
- Data races. Two threads reading and writing the same data simultaneously.
- Iterator invalidation. Modifying a collection while iterating over it.
- Use-after-free. Accessing memory that has been deallocated.
The borrow checker enforces these rules at compile time with zero runtime cost. If your code violates them, it will not compile.
Fix 1: Limit Reference Lifetimes
The simplest fix. Make the immutable borrow end before the mutable borrow begins:
Broken:
let mut data = vec![1, 2, 3];
let first = &data[0]; // Immutable borrow starts
data.push(4); // Mutable borrow — conflict!
println!("{}", first); // Immutable borrow still aliveFixed — use the immutable ref before mutating:
let mut data = vec![1, 2, 3];
let first = &data[0];
println!("{}", first); // Immutable borrow ends here (last use)
data.push(4); // Mutable borrow — no conflictRust uses Non-Lexical Lifetimes (NLL): a reference’s lifetime ends at its last use, not at the end of the scope. Moving the println! before push makes the immutable borrow end before the mutable borrow starts.
Pro Tip: When you get this error, look at where the immutable reference is last used. If you can move that use before the mutation, the error goes away. The borrow checker tracks the actual last usage point, not the scope boundary.
Fix 2: Clone the Data
If you need to read data and then modify the original, clone the value:
let mut data = vec![1, 2, 3];
let first = data[0]; // Copy the value (i32 implements Copy)
data.push(4); // No conflict — first is a copy, not a reference
println!("{}", first);For types that do not implement Copy:
let mut names = vec!["Alice".to_string(), "Bob".to_string()];
let first = names[0].clone(); // Clone the String
names.push("Charlie".to_string());
println!("{}", first); // Uses the clone, not a referenceTrade-off: Cloning allocates new memory. For large data or hot loops, consider restructuring instead.
Fix 3: Use Indices Instead of References
Avoid holding references to collection elements by using indices:
Broken:
let mut items = vec![1, 2, 3];
let item_ref = &items[0];
items.push(4); // Error!
println!("{}", item_ref);Fixed — use index:
let mut items = vec![1, 2, 3];
let idx = 0;
items.push(4);
println!("{}", items[idx]); // Access by index after mutationIndices are plain usize values — they do not borrow the collection.
Fix 4: Fix Struct Method Borrowing
A common issue when a struct method borrows &self and you want to modify a field:
Broken:
struct Game {
players: Vec<String>,
scores: Vec<i32>,
}
impl Game {
fn get_player(&self, idx: usize) -> &str {
&self.players[idx]
}
fn update_score(&mut self, idx: usize, score: i32) {
self.scores[idx] = score;
}
fn process(&mut self) {
let name = self.get_player(0); // Borrows &self (immutable)
self.update_score(0, 100); // Borrows &mut self — conflict!
println!("{}", name);
}
}Fixed — clone to break the borrow:
fn process(&mut self) {
let name = self.get_player(0).to_string(); // Clone breaks the borrow
self.update_score(0, 100);
println!("{}", name);
}Fixed — borrow fields separately:
fn process(&mut self) {
let name = &self.players[0]; // Borrows only self.players
self.scores[0] = 100; // Mutates only self.scores — no conflict!
println!("{}", name);
}Rust can track borrows of individual fields when you access them directly (not through methods). Method calls borrow the entire self.
Common Mistake: Using methods like
self.get_player()when direct field accessself.players[0]would avoid the borrow conflict. Methods borrow all ofself; field access borrows only that specific field.
Fix 5: Use Interior Mutability (RefCell, Cell)
When you need to mutate data behind an immutable reference, use interior mutability:
RefCell (runtime borrow checking):
use std::cell::RefCell;
let data = RefCell::new(vec![1, 2, 3]);
let borrowed = data.borrow(); // Immutable borrow
println!("{:?}", *borrowed);
drop(borrowed); // Must drop before mutable borrow
data.borrow_mut().push(4); // Mutable borrowCell (for Copy types):
use std::cell::Cell;
let counter = Cell::new(0);
counter.set(counter.get() + 1); // Mutate without &mutRefCell trade-off: Borrow rules are checked at runtime instead of compile time. Violating them causes a panic (BorrowMutError) instead of a compile error.
Fix 6: Fix Iterator and Loop Mutations
You cannot modify a collection while iterating over it:
Broken:
let mut items = vec![1, 2, 3, 4, 5];
for item in &items {
if *item > 3 {
items.retain(|&x| x <= 3); // Mutates during iteration!
}
}Fixed — collect modifications, apply after:
let mut items = vec![1, 2, 3, 4, 5];
items.retain(|&x| x <= 3); // Filter in one passFixed — use indices:
let mut items = vec![1, 2, 3, 4, 5];
let mut i = 0;
while i < items.len() {
if items[i] > 3 {
items.remove(i);
} else {
i += 1;
}
}Fixed — build a new collection:
let items = vec![1, 2, 3, 4, 5];
let filtered: Vec<_> = items.into_iter().filter(|&x| x <= 3).collect();For more on Rust’s borrow checker fundamentals, see Fix: Rust borrow checker error.
Fix 7: Use Mutex for Multi-Threaded Access
For shared mutable state across threads:
use std::sync::{Arc, Mutex};
use std::thread;
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
let data_clone = Arc::clone(&data);
let handle = thread::spawn(move || {
let mut locked = data_clone.lock().unwrap();
locked.push(4);
});
handle.join().unwrap();
println!("{:?}", data.lock().unwrap());RwLock for read-heavy workloads:
use std::sync::RwLock;
let data = RwLock::new(vec![1, 2, 3]);
// Multiple concurrent readers:
let read1 = data.read().unwrap();
let read2 = data.read().unwrap(); // OK — multiple reads allowed
drop(read1);
drop(read2);
// Exclusive writer:
let mut write = data.write().unwrap();
write.push(4);Fix 8: Split Borrows with Destructuring
When the borrow checker complains about borrowing parts of a struct:
struct State {
input: Vec<String>,
output: Vec<String>,
}
impl State {
fn process(&mut self) {
// Destructure to split the borrow:
let State { input, output } = self;
for item in input.iter() {
output.push(item.to_uppercase());
}
}
}Destructuring tells the compiler that input and output are separate fields, allowing simultaneous immutable borrow of input and mutable borrow of output.
Still Not Working?
Use Rc<RefCell<T>> for shared ownership with mutation:
use std::rc::Rc;
use std::cell::RefCell;
let shared = Rc::new(RefCell::new(vec![1, 2, 3]));
let clone = Rc::clone(&shared);
shared.borrow_mut().push(4);
println!("{:?}", clone.borrow());Consider unsafe as a last resort. Raw pointers bypass the borrow checker. Only use this when you are certain about memory safety and the safe alternatives are not viable:
let mut data = vec![1, 2, 3];
let ptr = data.as_mut_ptr();
unsafe {
*ptr = 10; // Direct memory access
}Check for lifetime annotation issues. Sometimes adding explicit lifetime annotations helps the compiler understand your intentions:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}Consider restructuring your data. If you frequently hit borrow checker issues with a specific data structure, the structure might not fit Rust’s ownership model. Consider using entity-component systems, arena allocators, or different data layouts.
For general Rust compilation errors, the Rust compiler’s error messages are unusually helpful — read the full output including the suggestions. The compiler often tells you exactly how to fix the issue.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Tauri Not Working — Command Not Found, IPC Error, File System Permission Denied, or Build Fails
How to fix Tauri app issues — Rust command registration, invoke IPC, tauri.conf.json permissions, fs scope, window management, and common build errors on Windows/macOS/Linux.
Fix: Rust Error Handling Not Working — ? Operator, Custom Error Types, and thiserror/anyhow
How to fix Rust error handling issues — the ? operator, From trait for error conversion, thiserror for custom errors, anyhow for applications, and Box<dyn Error> pitfalls.
Fix: Rust the trait X is not implemented for Y (E0277)
How to fix Rust compiler error E0277 'the trait X is not implemented for Y' with solutions for derive macros, manual trait implementations, Send/Sync bounds, trait objects, and generics.
Fix: Rust lifetime may not live long enough / missing lifetime specifier
How to fix Rust lifetime errors including missing lifetime specifier, may not live long enough, borrowed value does not live long enough, and dangling references.