Rust cannot borrow as mutable: what each error means and how to fix it

Summary

Rust's cannot borrow as mutable error is really a family of borrow-checker failures best triaged by error code: E0596 means the binding or self receiver was never mutable, E0502 means a shared borrow is still live at a mutation, and E0499 means two mutable borrows overlap, with the later-used-here label usually identifying the borrow that must be shortened. Most fixes reduce overlap by ending a borrow earlier, borrowing a field or element instead of the whole value, or copying/cloning when ownership semantics allow. Common patterns include switching methods to &mut self or using RefCell/Mutex when &self must stay, collecting or indexing when mutating a collection during iteration, using split_at_mut or get_disjoint_mut for disjoint elements, and using HashMap entry to avoid returning a reference before inserting. The final map look-up-then-insert case is sound but rejected by the current checker and is a target for Polonius, so stable Rust still expects the entry API there.

Rust's "cannot borrow as mutable" message covers several different mistakes, and each one has a different fix. Start with the error code. E0596 means the value was never mutable in the first place. E0502 means a shared borrow is still alive when you try to mutate. E0499 means you asked for two mutable borrows of the same thing. Then look for the label that says "later used here". That line, not the one under the caret, is usually the one you need to change.

Every example below was compiled with rustc 1.98.0 on edition 2024, and the error output is copied from those runs. The current stable release is Rust 1.100.0, from 12 November 2026. Its announcement lists no borrow checker changes, so you get the same errors and the same fixes there.

The rule behind every variant

The Rust Book's chapter on references puts it in one sentence: "At any given time, you can have either one mutable reference or any number of immutable references." The part that trips people up is how long a reference counts as alive. Since non-lexical lifetimes, a reference's scope "starts from where it is introduced and continues through the last time that reference is used", not to the end of the block. So nearly every fix is one of these: end the borrow sooner, borrow a smaller piece (one field or one element instead of the whole value), or stop holding a reference and copy or clone the data instead.

E0596: the value was never mutable

The simplest case is a binding declared without mut:

fn main() {
    let names = Vec::new();
    names.push("ann");
    println!("{names:?}");
}
error[E0596]: cannot borrow `names` as mutable, as it is not declared as mutable
 --> e0596_let.rs:3:5
  |
3 |     names.push("ann");
  |     ^^^^^ cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
2 |     let mut names = Vec::new();
  |         +++

Here the help line is the fix. The variant that wastes more time shows up inside a method:

struct Cart {
    items: Vec<String>,
}

impl Cart {
    fn add(&self, item: &str) {
        self.items.push(item.to_string());
    }
}
error[E0596]: cannot borrow `self.items` as mutable, as it is behind a `&` reference
 --> e0596.rs:7:9
  |
7 |         self.items.push(item.to_string());
  |         ^^^^^^^^^^ `self` is a `&` reference, so it cannot be borrowed as mutable
  |
help: consider changing this to be a mutable reference
  |
6 |     fn add(&mut self, item: &str) {
  |             +++

Inside a &self method, every field is reached through a shared reference, so nothing inside is mutable however the caller declared the struct. Changing the signature to &mut self is the right fix when the method really does change state. The error then moves to the caller: with let cart = Cart { ... }, the call cart.add("book") fails with "cannot borrow `cart` as mutable, as it is not declared as mutable", and let mut cart clears it. If the method has to stay &self, for example because the value is shared through an Rc or across threads, that is the job of interior mutability: RefCell<T> on one thread, Mutex<T> across several.

E0502: a shared borrow is still in use

This is the error most people mean when they search for "cannot borrow as mutable":

fn main() {
    let mut scores = vec![10, 20, 30];
    let first = &scores[0];
    scores.push(40);
    println!("first = {first}");
}
error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
 --> e0502.rs:4:5
  |
3 |     let first = &scores[0];
  |                  ------ immutable borrow occurs here
4 |     scores.push(40);
  |     ^^^^^^^^^^^^^^^ mutable borrow occurs here
5 |     println!("first = {first}");
  |                        ----- immutable borrow later used here

The compiler isn't being pedantic here. As the Book's chapter on vectors explains, push may need to allocate a bigger buffer and copy the elements across, which would leave first pointing at freed memory. The fix depends on what you need from first. If it is a small Copy value, take the value rather than a reference. If it has to be a reference, finish using it before you mutate:

fn main() {
    let mut scores = vec![10, 20, 30];
    let first = scores[0]; // i32 is Copy: no borrow is held
    scores.push(40);
    println!("first = {first}");

    let mut names = vec![String::from("ann"), String::from("bob")];
    let longest = &names[0];
    println!("longest = {longest}"); // last use of the borrow
    names.push(String::from("cy")); // fine: the borrow already ended
    println!("{names:?}");
}

Moving the println! above the push is enough, because the borrow ends at its last use. For a String you can't copy and can't move out of the way, .clone() is a perfectly reasonable answer. It costs one allocation and the borrow problem goes away.

Pushing to a Vec while looping over it

The same error shows up in loops, where it is less obvious that a borrow is being held:

fn main() {
    let mut tasks = vec![1, 2, 3];
    for t in &tasks {
        if *t == 2 {
            tasks.push(99);
        }
    }
    println!("{tasks:?}");
}
error[E0502]: cannot borrow `tasks` as mutable because it is also borrowed as immutable
 --> iter_push.rs:5:13
  |
3 |     for t in &tasks {
  |              ------
  |              |
  |              immutable borrow occurs here
  |              immutable borrow later used here
4 |         if *t == 2 {
5 |             tasks.push(99);
  |             ^^^^^^^^^^^^^^ mutable borrow occurs here

for t in &tasks holds a shared borrow for the whole loop, because the iterator is used again on every pass. Collect what you want to add, then add it after the loop ends. Or loop by index, so each access is a fresh, short borrow:

fn main() {
    let mut tasks = vec![1, 2, 3];
    let extra: Vec<i32> = tasks.iter().filter(|&&t| t == 2).map(|_| 99).collect();
    tasks.extend(extra);
    println!("{tasks:?}");

    // Or loop by index, re-borrowing on every step.
    let mut tasks = vec![1, 2, 3];
    let mut i = 0;
    while i < tasks.len() {
        if tasks[i] == 2 {
            tasks.push(99);
        }
        i += 1;
    }
    println!("{tasks:?}");
}

The index loop sees elements added during the loop, because tasks.len() is read again on every pass. Whether that's what you want depends on the job, so choose deliberately.

Calling a &mut self method while borrowing a field

This one confuses people because the two borrows touch different fields:

struct Queue {
    jobs: Vec<String>,
    log: Vec<String>,
}

impl Queue {
    fn record(&mut self, msg: String) {
        self.log.push(msg);
    }

    fn run(&mut self) {
        for job in &self.jobs {
            self.record(format!("ran {job}"));
        }
    }
}
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
  --> method_self.rs:13:13
   |
12 |         for job in &self.jobs {
   |                    ----------
   |                    |
   |                    immutable borrow occurs here
   |                    immutable borrow later used here
13 |             self.record(format!("ran {job}"));
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

Note the *self in the message. Calling a method that takes &mut self mutably borrows the whole struct, so the call conflicts with the loop's borrow of self.jobs, even though record only ever touches log. Field access is different. The Rustonomicon's page on splitting borrows confirms the checker does understand disjoint fields of a struct. So write the field access inline, or turn the helper into a function that takes only &mut Vec<String>:

impl Queue {
    fn run(&mut self) {
        // Borrowing two different fields directly is allowed.
        for job in &self.jobs {
            self.log.push(format!("ran {job}"));
        }
    }
}

E0499: two mutable references into one Vec

fn main() {
    let mut balances = vec![100, 50, 75];
    let from = &mut balances[0];
    let to = &mut balances[1];
    *from -= 30;
    *to += 30;
    println!("{balances:?}");
}
error[E0499]: cannot borrow `balances` as mutable more than once at a time
 --> e0499.rs:4:19
  |
3 |     let from = &mut balances[0];
  |                     -------- first mutable borrow occurs here
4 |     let to = &mut balances[1];
  |                   ^^^^^^^^ second mutable borrow occurs here
5 |     *from -= 30;
  |     ----------- first borrow later used here
  |
  = help: use `.split_at_mut(position)` to obtain two mutable non-overlapping sub-slices

Indices 0 and 1 are obviously different to a human. The Rustonomicon is blunt about why the compiler doesn't see it: borrowck "doesn't understand arrays or slices in any way", so indexing borrows the whole vector. The standard library has safe methods that check the indices for you. split_at_mut is the one rustc suggests. slice::get_disjoint_mut, stable since Rust 1.86.0, hands back several mutable references in one call and returns an error if any index is out of bounds or the indices overlap:

fn main() {
    let mut balances = vec![100, 50, 75];

    // Option 1: ask for both elements in one call (stable since Rust 1.86).
    if let Ok([from, to]) = balances.get_disjoint_mut([0, 1]) {
        *from -= 30;
        *to += 30;
    }

    // Option 2: split the slice into two non-overlapping halves.
    let (left, right) = balances.split_at_mut(1);
    left[0] -= 10;
    right[0] += 10;

    println!("{balances:?}");
}

HashMap has a get_disjoint_mut of its own, also from 1.86.0, but it behaves differently. It returns an array of Options, with None for missing keys, and it panics rather than returning an error if two keys are the same. If the keys come from user input, check them yourself before calling it.

Returning a reference from a map, then inserting

The last case is one the borrow checker gets wrong. This code is sound, and it is still rejected:

use std::collections::HashMap;

fn label(cache: &mut HashMap<u32, String>, id: u32) -> &String {
    if let Some(name) = cache.get(&id) {
        return name;
    }
    cache.insert(id, format!("user-{id}"));
    cache.get(&id).unwrap()
}
error[E0502]: cannot borrow `*cache` as mutable because it is also borrowed as immutable
 --> map_return.rs:7:5
  |
3 | fn label(cache: &mut HashMap<u32, String>, id: u32) -> &String {
  |                 - let's call the lifetime of this reference `'1`
4 |     if let Some(name) = cache.get(&id) {
  |                         ----- immutable borrow occurs here
5 |         return name;
  |                ---- returning this value requires that `*cache` is borrowed for `'1`
6 |     }
7 |     cache.insert(id, format!("user-{id}"));
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

Because name is returned on one path, the current checker treats the get borrow as lasting for the whole function, even on the path where nothing was found. This is "problem case #3" in the non-lexical lifetimes RFC, a pattern the RFC itself says "cannot be so easily resolved". Don't fight it with lifetimes. The entry API does the lookup and the insert in one step and compiles cleanly:

fn label(cache: &mut HashMap<u32, String>, id: u32) -> &String {
    cache.entry(id).or_insert_with(|| format!("user-{id}"))
}

When entry doesn't fit, for example because the key is expensive to build up front, checking contains_key first and indexing at the end also compiles. It just hashes the key twice. A real fix is coming: the Rust team enabled Polonius Alpha on nightly in August 2026, and the announcement's main example of code it accepts is this same look-up-or-insert shape. The team aims to stabilize it before the end of 2026, but it is not in 1.100.0, so on stable the entry API is still the answer.

RUST BORROW CHECKER CANNOT BORROW AS MUTABLE E0502 E0499 E0596 GET_DISJOINT_MUT

  RELATED

  COMMENTS

0

No comment for this article.