Skip to content

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

FixDevs · (Updated: )

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 here

Or variations:

error[E0499]: cannot borrow `data` as mutable more than once at a time
error[E0502]: cannot borrow `self.field` as mutable because it is also borrowed as immutable

Rust’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 alive

Fixed — 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 conflict

Rust 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 reference

Trade-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 mutation

Indices 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 access self.players[0] would avoid the borrow conflict. Methods borrow all of self; 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 borrow

Cell (for Copy types):

use std::cell::Cell;

let counter = Cell::new(0);
counter.set(counter.get() + 1);  // Mutate without &mut

RefCell 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 pass

Fixed — 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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles