Skip to content
rusthacks.dev
Rust

Rust Error E0499 Cannot Borrow as Mutable More Than Once

E0499 means two mutable references to the same value were alive at once. Learn to spot which two, and fix it with scope, splitting or an index.

6 min read
Summarize in AI
Flat illustration for the post “Rust Error E0499 Cannot Borrow as Mutable More Than Once”, composed around borrow.

error[E0499]: cannot borrow ... as mutable more than once at a time means you asked for two &mut references to the same thing while the first is still in use. Rust allows exactly one mutable reference to a value at a time, and the compiler enforces it so that two parts of a program can never modify the same data at once. The error is common, and its causes fall into two groups: genuinely overlapping access that you should restructure, and access to different parts of a value that the compiler cannot prove are different. This post shows both with the compiler's real output from Rust 1.99, and the fix for each.

The rule the error enforces

At any point in a program a value can have either any number of shared references (&T) or exactly one mutable reference (&mut T), never both and never two mutable ones. The reason is practical. A &mut is a promise that nothing else can see the data while you change it, and the compiler uses that promise to optimize and to rule out data races and iterator invalidation at compile time. Two live &mut references to one value would break the promise.

Cause one: two indexes into the same collection

The first reproduction is the one that surprises people, because the two elements are clearly different:

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

Elements 0 and 1 are different memory, but the compiler cannot see that. scores[0] is sugar for IndexMut::index_mut(&mut scores, 0), a method call that borrows the whole vector for as long as the returned reference lives. From the type checker's view, both lines borrow all of scores. The error names the three facts: where the first borrow starts, where the second conflicts, and where the first is used again, which is what keeps it alive.

The compiler's help line is the right fix. split_at_mut splits a slice into two non-overlapping mutable halves, and the borrow checker accepts that because the standard library proves the halves are disjoint. Since Rust 1.86 there is a more direct tool, get_disjoint_mut, which takes several indices and checks at run time that they are distinct:

src/main.rsrust
fn main() {
    let mut scores = vec![10, 20, 30];
    let (left, right) = scores.split_at_mut(1);
    left[0] += 1;
    right[0] += 1;
    println!("{scores:?}");

    // Since Rust 1.86, `get_disjoint_mut` hands out several &mut at once and checks the indices.
    if let Ok([a, c]) = scores.get_disjoint_mut([0, 2]) {
        std::mem::swap(a, c);
    }
    println!("{scores:?}");
}
Text
[11, 21, 30]
[30, 21, 11]

The first call split the vector after element 0, so left is [10] and right is [20, 30], and the two increments changed elements 0 and 1. The second call borrowed elements 0 and 2 together and swapped them. If you pass the same index twice, get_disjoint_mut returns an Err instead of undefined behavior, which is the check the compiler could not do at compile time. For swapping specifically, slice::swap(i, j) does the whole job without holding two references.

Cause two: a method that borrows all of self

The second form involves your own types:

src/main.rsrust
struct Bank {
    accounts: Vec<i64>,
}

impl Bank {
    fn account(&mut self, i: usize) -> &mut i64 {
        &mut self.accounts[i]
    }
}

fn main() {
    let mut bank = Bank { accounts: vec![100, 50] };
    let from = bank.account(0);
    let to = bank.account(1);
    *from -= 25;
    *to += 25;
    println!("{:?}", bank.accounts);
}
Text
error[E0499]: cannot borrow `bank` as mutable more than once at a time
  --> e0499b.rs:14:14
   |
13 |     let from = bank.account(0);
   |                ---- first mutable borrow occurs here
14 |     let to = bank.account(1);
   |              ^^^^ second mutable borrow occurs here
15 |     *from -= 25;
   |     ----------- first borrow later used here

The method signature fn account(&mut self, i: usize) -> &mut i64 ties the returned reference to the borrow of self. While from is alive, bank is considered mutably borrowed, so a second call is refused, even though the two accounts are different elements. The compiler reasons from signatures, not from bodies, and the signature says "this returns something borrowed from the whole bank".

There are three ways out, in order of preference. First, move the operation into the type that owns the data. A transfer does not need two simultaneous references if the bank performs it:

src/main.rsrust
impl Bank {
    fn transfer(&mut self, from: usize, to: usize, amount: i64) {
        self.accounts[from] -= amount;
        self.accounts[to] += amount;
    }
}
Text
[75, 75]

Each statement borrows self.accounts for one expression and releases it, so there is never an overlap. Moving the logic to the owner is the usual answer, and it also puts the invariant, "money is conserved", in one place. Second, if you really need two references, borrow different fields directly, because the compiler can see that self.a and self.b are disjoint when you write the field access in the same function. Third, if the two pieces are genuinely independent, split the struct into two, or hold the pieces in separate RefCells and take the borrows at run time, accepting a possible panic.

Cause three: a reference held across a loop iteration

A subtler form keeps a mutable reference alive across an iteration. Pushing into a collection while a &mut to one of its elements is held, or calling .iter_mut() and then mutating the collection inside the loop, produces E0499 for the same reason: the loop holds a mutable borrow for its whole body. The fix is to restructure so that the borrow ends before the next use, by collecting the changes first and applying them after the loop, or by iterating over indices when you need to touch the collection itself.

When it is a real bug

Not every E0499 is the compiler being conservative. If the two references really do point to the same data, the program has a logic problem, and the error has found it. A function fn merge(a: &mut Node, b: &mut Node) called as merge(&mut tree, &mut tree) would corrupt the tree in a language without this check. Before reaching for unsafe, RefCell or index tricks, ask whether the two accesses are meant to be the same object. If they might be, the check is protecting you, and the fix is a design that never needs both at once.

How to avoid it

  • Prefer methods that perform a whole operation (transfer, swap, merge) over methods that hand out a reference and expect the caller to use two of them.
  • When a function returns &mut, remember the returned reference holds the entire receiver until it is dropped; finish with it before calling another method.
  • Reach for split_at_mut, get_disjoint_mut, chunks_mut and iter_mut for slices; they exist to give out several &mut safely.
  • Use std::mem::swap, std::mem::replace and Option::take to move data between places without holding two references.
  • Keep each &mut short. Using the reference immediately and dropping it is the way to avoid most overlaps.

The neighboring errors are E0382, use of a moved value, and E0502, mutable and immutable borrow together. All three come from the same rule, and the three-column view of when each applies is in ownership in Rust explained.

Taggede0499
Share

Keep reading

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.