Error

"cannot borrow as mutable because it is also borrowed as immutable" — How to Fix

Fix the Rust borrow checker error by ensuring immutable and mutable references to the same data do not overlap in scope.

The error E0502: cannot borrow as mutable because it is also borrowed as immutable

You are writing a function to update a configuration map. You look up a key to check its current value. If the value is missing, you want to insert a default. You write the lookup, store the result in a variable, and then call insert on the map. The compiler stops you with E0502.

This error appears when you try to mutate data while an immutable reference to that same data is still alive. Rust forbids this combination. You can have many readers, or one writer, but never both at the same time. The error is not a suggestion. It is a guarantee that your code will not create dangling pointers or data races.

Why Rust forbids aliasing and mutation

Rust enforces a rule called "aliasing XOR mutation." You can have multiple aliases (references) to data, but none of them can be mutable. Or you can have one mutable alias, but no other aliases can exist. This rule protects memory safety.

Consider a Vec. The vector stores its elements on the heap. When you push a new element, the vector might need more space. The allocator provides a new, larger chunk of memory, copies the existing elements, and frees the old chunk. If you held an immutable reference to an element while the vector reallocated, that reference would still point to the old memory address. The old memory is now freed. Your reference is a dangling pointer. Dereferencing it reads garbage or crashes the program.

E0502 prevents this. If you hold a reference, the compiler ensures the container cannot reallocate or change in a way that invalidates the reference. If you need to mutate the container, the compiler ensures no references exist to protect you from dangling pointers.

Think of a mutable borrow like holding a marker to a whiteboard. An immutable borrow is someone pointing at the board and reading. Rust says: if you are holding the marker, no one else can point at the board. If you erase something while they are pointing, their finger is now pointing at nothing, or worse, at the wrong thing. The rule keeps the view consistent with the reality.

Minimal example

The error triggers when a mutable operation overlaps with an immutable borrow.

fn main() {
    let mut numbers = vec![1, 2, 3];

    // Immutable borrow starts here.
    // The compiler tracks that `first` holds a reference into `numbers`.
    let first = &numbers[0];

    // Mutable borrow attempt.
    // `push` needs `&mut self`. The compiler sees `first` is still alive.
    // E0502: cannot borrow `numbers` as mutable because it is also borrowed as immutable.
    numbers.push(4);

    println!("First: {first}");
}

The compiler rejects this with E0502. The variable first holds a reference. The call to push requires exclusive access to numbers. The scopes overlap. The compiler cannot prove that push will not invalidate first.

Fixing with scope separation

If the immutable borrow is only needed for a short moment, drop it before the mutation.

fn main() {
    let mut numbers = vec![1, 2, 3];

    // Scope for the immutable borrow.
    {
        let first = &numbers[0];
        println!("First: {first}");
        // `first` is dropped here. The borrow ends.
    }

    // Mutable borrow is now safe. No immutable references exist.
    numbers.push(4);
}

The block creates a scope. When the block ends, first is dropped. The borrow checker sees the borrow is gone. The mutation is allowed.

Drop the reference before you mutate. If the data changes, no one should be holding a stale view.

Realistic example: processing and appending

A common pattern is reading a value, transforming it, and appending the result to the same collection.

fn process_and_append(data: &mut Vec<String>) {
    // We want to read the first string, transform it, and append the result.
    // This creates a borrow conflict.
    let first = &data[0]; // Immutable borrow of `data`.
    let transformed = first.to_uppercase();
    
    // E0502: `data` is borrowed immutably by `first`.
    // `push` requires mutable access.
    data.push(transformed);
}

The variable first holds a reference into data. The call to push needs mutable access. The conflict persists until first is used. Even though transformed is a new String, the reference first is still alive.

Fixing with cloning

When you need the value but not the reference, clone the value. Cloning breaks the borrow chain.

fn process_and_append(data: &mut Vec<String>) {
    // Clone the value out of the vector.
    // This creates a new `String` and drops the borrow immediately.
    let first = data[0].clone();
    
    let transformed = first.to_uppercase();
    
    // `data` is free to mutate. No references remain.
    data.push(transformed);
}

Cloning copies the data. The vector is no longer borrowed. The mutation is safe.

Convention aside: When you clone to fix a borrow error, clone the minimal amount. Clone the value, not the container. data[0].clone() is preferred over data.clone(). The community expects you to pay only for what you use. Cloning the whole vector when you only need one element signals that you are avoiding the borrow checker rather than solving the problem.

Clone the value, not the world. If you only need a piece of data, extract that piece.

Advanced fix: splitting disjoint access

Sometimes you need to read one part of a collection and write to another part simultaneously. The compiler can allow this if you prove the parts do not overlap. The method split_at_mut creates two mutable slices from disjoint ranges.

fn update_first_from_last(data: &mut Vec<i32>) {
    // Split the vector into two disjoint mutable slices.
    // `left` covers indices 0..len-1.
    // `right` covers index len-1..len.
    let (left, right) = data.split_at_mut(data.len() - 1);
    
    // `right` has one element. We can read it.
    let last_value = right[0];
    
    // `left` is mutable and disjoint from `right`.
    // We can write to `left` while holding `right`.
    left[0] = last_value;
}

split_at_mut returns two &mut slices. The compiler knows they point to non-overlapping memory. You can read from one and write to the other. This pattern is essential for algorithms like in-place sorting or swapping elements.

Use split_at_mut when you need to access two parts of a collection at once. The compiler will reject a single borrow, but it accepts split borrows that prove disjointness.

Pitfalls and hidden borrows

The borrow checker tracks references precisely, but hidden borrows can cause E0502 in unexpected places.

Non-lexical lifetimes

Rust uses Non-Lexical Lifetimes (NLL). The borrow ends at the last use of the variable, not at the end of the scope. This helps, but it can be confusing.

fn main() {
    let mut v = vec![1, 2, 3];
    let x = &v[0];
    v.push(4); // Error: `x` is used later.
    println!("{x}");
}

The error occurs at push, not at println. The compiler sees x is used after push. The borrow must stay alive until println. Moving the use of x before the mutation fixes the error.

fn main() {
    let mut v = vec![1, 2, 3];
    let x = &v[0];
    println!("{x}"); // Last use of `x`. Borrow ends here.
    v.push(4);       // Safe.
}

Trust NLL. The compiler calculates the exact end of the borrow. If the error persists, the borrow is needed later than you think.

Closures capturing references

Closures can capture references implicitly. A closure that captures a reference extends the borrow's lifetime.

fn main() {
    let mut v = vec![1, 2, 3];
    let ref_to_first = &v[0];
    
    // This closure captures `v` by reference?
    // No, `iter` borrows `v`.
    // But if we store the closure, the borrow persists.
    let _closure = || {
        // If this closure used `ref_to_first`, it would capture it.
        // Capturing extends the borrow.
    };
    
    v.push(4); // Error if closure holds a borrow.
}

If a closure captures a reference, the borrow lives as long as the closure. Dropping the closure drops the borrow. Be careful when storing closures in structs or returning them from functions. The borrow can leak out of the function scope.

Check your closures. If a closure captures a reference, that reference stays alive.

Decision matrix

When you hit E0502, choose the fix based on your data and logic.

Use cloning when the data is small or cheap to copy, and you need to break the borrow chain to mutate the container. Cloning is the simplest fix for extracting a value.

Use reordering when you can perform the mutation before the immutable borrow starts. Move the use of the reference earlier in the code. NLL often makes this possible without changing logic.

Use split_at_mut when you need to access two disjoint parts of a slice or vector simultaneously, one mutable and one immutable. This is the correct tool for in-place algorithms.

Use RefCell<T> when the borrow conflict only appears at runtime and you cannot restructure the logic, though this is a last resort. RefCell moves the borrow check to runtime and panics if you violate the rules. Prefer compile-time checks.

Use Rc<T> or Arc<T> when you need shared ownership across multiple owners, but this does not solve mutable access conflicts. Shared ownership requires interior mutability like RefCell or Mutex to mutate.

Reach for plain references when lifetimes are simple. The unsafe alternative is rarely worth it. If you find yourself reaching for unsafe to silence E0502, you are likely hiding a logic error.

Where to go next