The two keys problem
You are building a physics engine for a 2D game. You have a Particle struct with position and velocity fields. Your update function needs to read the velocity to calculate the new position, then write the result back. You write a helper function that takes mutable references to both fields so you can pass them to a generic math routine. The compiler rejects the code with E0499.
The error message says "cannot borrow as mutable more than once." You only have one particle. You aren't trying to mutate two different particles. You just want to update two fields of the same object. The compiler sees two mutable borrows of the same data and blocks you. This is the borrow checker enforcing exclusive access.
Exclusive access is the rule
Rust's borrowing rules are simple but strict. You can have any number of immutable borrows (&T) at the same time, or exactly one mutable borrow (&mut T). You cannot have a mutable borrow and an immutable borrow active simultaneously, and you cannot have two mutable borrows active simultaneously.
E0499 triggers when you violate the second part of that rule. You are asking for a second mutable borrow while the first one is still alive. Mutable borrows represent exclusive access. If two parts of your code both hold a mutable reference to the same value, they can both modify it. The order of modifications becomes unpredictable. This leads to data races and undefined behavior in concurrent code, or silent corruption in single-threaded code. Rust prevents this at compile time.
Think of a mutable reference like a master key to a safe. The safe contains a secret value. If you hand the master key to Alice, she can open the safe and change the contents. If you then hand the master key to Bob, Bob can also open the safe. Alice and Bob might try to change the contents at the same time. The safe's contents become garbage. Rust's borrow checker acts as the key custodian. It will only hand out one master key at a time. If you ask for a second key while the first is out, the custodian says no. That refusal is E0499.
Minimal example
The error appears whenever two mutable references to the same variable overlap in scope.
fn main() {
let mut x = 42;
// r1 takes exclusive access to x.
// The compiler now considers x "borrowed mutably" by r1.
let r1 = &mut x;
// r2 tries to take exclusive access to x.
// The compiler sees r1 is still alive and active.
// Two exclusive claims on x is a contradiction.
let r2 = &mut x; // E0499: cannot borrow `x` as mutable more than once
*r1 += 1;
*r2 += 1;
}
The compiler rejects this code before it runs. It does not matter that you intend to use r1 and r2 sequentially. The variables r1 and r2 both exist in the same scope, and both hold mutable references. The compiler assumes they could be used in any order. The overlap is real.
Modern Rust uses Non-Lexical Lifetimes (NLL). The compiler tracks the last use of a variable, not just the end of the block. If you remove the use of r1 after r2, the error might disappear because the compiler sees r1 is dead. However, if you use both references, or if the compiler cannot prove they are disjoint, E0499 remains. The rule holds: no two active mutable borrows of the same data.
Trust the borrow checker here. It is preventing a class of bugs that causes hours of debugging in other languages.
Real-world scenario: swapping elements
The most common realistic trigger for E0499 is indexing a collection twice. You want to swap two elements in a vector. You grab mutable references to both indices.
fn swap_elements(v: &mut Vec<i32>, i: usize, j: usize) {
// Attempt to get mutable references to two indices.
// The compiler sees two mutable borrows of `v`.
// It cannot prove `i` and `j` are different.
let a = &mut v[i]; // E0499: cannot borrow `*v` as mutable more than once
let b = &mut v[j];
std::mem::swap(a, b);
}
The compiler blocks this because it does not know if i equals j. If i equals j, you would have two mutable references to the exact same element. That violates the exclusivity rule. Even if you know i and j are different, the compiler requires a proof. You cannot just assert it. You must structure the code so the compiler can verify the disjointness.
The standard solution is split_at_mut. This method splits a slice into two mutable slices at a given index. The compiler knows the two slices are disjoint because the index defines a hard boundary.
fn swap_elements_safe(v: &mut Vec<i32>, i: usize, j: usize) {
// Ensure i is the smaller index for split_at_mut.
let (lo, hi) = if i < j { (i, j) } else { (j, i) };
// split_at_mut returns two mutable slices.
// The compiler proves these slices never overlap.
// left covers indices 0..lo.
// right covers indices lo..len.
let (left, right) = v.split_at_mut(lo);
// Access the element in the left slice.
// We know lo > 0 is required for this to be safe.
let a = &mut left[left.len() - 1];
// Access the element in the right slice.
// The offset is adjusted because right starts at lo.
let b = &mut right[hi - lo];
std::mem::swap(a, b);
}
split_at_mut is the idiomatic way to get two mutable references into a slice or vector. It provides the proof the compiler needs. The two returned slices are guaranteed disjoint. You can safely mutate both.
Use split_at_mut whenever you need to access two parts of a collection mutably. It is safer and faster than interior mutability.
Pitfalls and compiler signals
E0499 often appears in more complex scenarios than simple variable borrows. Hidden borrows and struct field access can trigger the error.
Struct field borrowing
Borrowing two fields of a struct mutably can trigger E0499. The compiler treats the struct as a single unit. If you borrow s.a mutably, you have borrowed s mutably. Borrowing s.b mutably is a second mutable borrow of s.
struct Config {
width: u32,
height: u32,
}
fn resize(config: &mut Config) {
let w = &mut config.width;
let h = &mut config.height; // E0499: cannot borrow `*config` as mutable more than once
*w += 10;
*h += 10;
}
The fix is to avoid holding both references simultaneously. Update the fields directly, or use a block scope to limit the lifetime of the first borrow.
fn resize_fixed(config: &mut Config) {
// Block scope limits the lifetime of w.
// w is dropped at the end of the block.
{
let w = &mut config.width;
*w += 10;
}
// w is gone. config is free to be borrowed again.
let h = &mut config.height;
*h += 10;
}
The drop convention
When you need to explicitly end a borrow before the variable goes out of scope, use drop. This is a community convention. It signals to readers and the compiler that you are intentionally releasing the borrow.
fn process_and_update(buffer: &mut Vec<u8>) {
let header = &mut buffer[0];
*header = 0xFF;
// Explicitly drop the borrow.
// This tells the compiler and readers the borrow is over.
drop(header);
// Now we can mutate the rest of the buffer.
buffer.push(0x00);
}
drop takes ownership of the variable and immediately drops it. The borrow ends. This is cleaner than adding an empty block scope just to limit a lifetime.
Interior mutability trade-offs
When you cannot prove disjointness at compile time, you might reach for RefCell. RefCell moves the borrow checks to runtime. It allows multiple references to the data, but enforces the borrowing rules dynamically.
use std::cell::RefCell;
struct SharedData {
value: RefCell<Vec<i32>>,
}
fn risky_swap(data: &SharedData, i: usize, j: usize) {
// borrow_mut returns a RefMut guard.
// If another borrow is active, this panics.
let a = data.value.borrow_mut();
let b = data.value.borrow_mut(); // Panics at runtime if a is active
// This code never runs because the second borrow panics.
}
RefCell is useful when the borrow checker is too conservative. It is also a trap. The runtime panic happens in production, not at compile time. The error is harder to debug. The performance cost is higher because of the runtime counter. Use RefCell only when you cannot restructure the code to satisfy the borrow checker.
Convention aside: RefCell::clone is rarely what you want. Cloning a RefCell clones the cell, not the contents. It creates a new cell with a copy of the value. If you want to share the cell, clone the Rc or Arc that wraps it. If you just call .clone() on a RefCell, you get a separate cell. This is a common source of confusion. Write RefCell::clone(&cell) to be explicit, or better, avoid cloning RefCell entirely.
Compiler error inline
When you see E0499, the compiler message points to the second mutable borrow. It says "cannot borrow as mutable more than once." It also lists the first borrow and its lifetime. Read the lifetime information. It tells you exactly where the first borrow starts and where it ends. The fix is almost always to shorten the first borrow or delay the second borrow.
The compiler is precise. It shows you the conflict. Use the information to restructure the code. Do not ignore the error by adding unsafe. unsafe does not fix the logic error; it just hides it.
Choosing the right tool
You have several options when E0499 blocks you. Pick the tool that matches your situation.
Use a block scope { ... } when the first borrow is temporary and you can finish using it before the second borrow starts. This is the cheapest fix. It requires no runtime overhead and keeps the code safe.
Use split_at_mut when you need two mutable references to different parts of a slice or vector and can prove the indices are disjoint. This is the idiomatic solution for collection indexing. It provides compile-time guarantees.
Use std::mem::take when you need to extract a value from a mutable reference to break the borrow chain. This replaces the value with a default and returns the original. It is useful for clearing buffers or swapping state without holding references.
Use RefCell<T> when you cannot prove disjointness at compile time and are okay with a runtime panic if you violate the rules. This is a fallback for complex data structures where the borrow checker cannot follow the logic. Accept the runtime cost.
Use Cell<T> when the value is Copy and you need interior mutability without the overhead of RefCell. Cell provides set and get methods that copy the value. It does not allow references. It is faster than RefCell but only works for Copy types.
Reach for unsafe only when you are implementing a safe abstraction yourself, like a custom allocator or a low-level data structure. Never use unsafe to bypass E0499 in application code. The abstraction must provide a safe interface. If you cannot write a // SAFETY: comment proving the invariants, you do not have a safe abstraction.