How to fix Rust E0502 cannot borrow as mutable

Fix Rust E0502 by ensuring immutable borrows end before starting mutable borrows to prevent overlapping access.

The borrow conflict

You are writing a function to process a list of user inputs. You loop through the list to validate each entry, and when you find an invalid one, you want to remove it from the list. You grab a reference to the current item to check it, then try to call remove on the list. The compiler stops you dead with E0502: "cannot borrow as mutable because it is also borrowed as immutable."

It feels like the compiler is being pedantic. You know the logic is sound. You just want to read a value and update the collection. But Rust is preventing a crash that would happen the moment you ran this code. If Rust allowed you to remove an item while a reference to it existed, that reference would point to freed memory or shifted data. Dereferencing it would read garbage or segfault. E0502 is the compiler enforcing memory safety before the program starts.

The rule: one writer or many readers

Rust's borrowing rules are strict but simple. You can have as many immutable references to a value as you want, or exactly one mutable reference. Never both at the same time.

Think of a shared whiteboard in a meeting room. Anyone can walk up and read what is written. That is safe. Ten people can read it simultaneously without interfering with each other. But if one person grabs a marker to erase and rewrite, everyone else has to step back. If someone is reading while you are erasing, they might see half-erased text. If the board structure is changing, the reading might become impossible. Rust prevents the "reading while erasing" scenario at compile time. E0502 is the compiler pointing out that you are holding a marker while someone else is still reading.

The rule protects against data races and dangling pointers. An immutable reference assumes the data will not change. A mutable reference assumes exclusive access to change the data. Allowing both breaks these assumptions.

Minimal example

Here is the simplest case that triggers E0502. You create an immutable reference, then try to mutate the value.

fn main() {
    let mut data = String::from("hello");
    
    // Create an immutable reference.
    // The compiler tracks that `data` is being read.
    let reference = &data;
    
    // Attempt to mutate `data` while `reference` is alive.
    // This would invalidate `reference` if allowed.
    // data.push_str(" world"); // ERROR: E0502
    
    // The compiler rejects this with E0502:
    // cannot borrow `data` as mutable because it is also borrowed as immutable
    
    // Fix: Use the reference first, then mutate.
    println!("Current value: {}", reference);
    
    // `reference` is no longer used after this line.
    // Non-Lexical Lifetimes allow mutation here.
    data.push_str(" world");
    println!("Updated value: {}", data);
}

The fix relies on the order of operations. The immutable borrow must end before the mutable borrow begins. In this case, the borrow ends after the println! because that is the last use of reference.

Trust the borrow checker. It tracks the last use of every reference. If you move the mutation after the read, the error disappears.

Non-Lexical Lifetimes help, but have limits

Modern Rust uses Non-Lexical Lifetimes, or NLL. Borrows end at the last use of the reference, not at the end of the block where the reference was created. This saves you from writing manual scope blocks in many cases.

NLL has limits. If you store the reference in a struct, pass it to a function that might hold it, or use it in a closure, the compiler cannot prove when the borrow ends. In those cases, the borrow lasts until the end of the scope, and E0502 returns.

When NLL cannot help, you must structure the code to separate the borrows explicitly. Scope blocks, value extraction, and indices are the tools for this.

Realistic scenario: updating a struct

A common trap occurs when working with structs. You want to read one field and update another. Even if the fields are independent, borrowing a field borrows the whole struct.

struct Config {
    name: String,
    version: u32,
}

impl Config {
    // This fails with E0502.
    // fn bump_version_bad(&mut self) {
    //     let current_name = &self.name; // Immutable borrow of self
    //     self.version += 1;             // Mutable borrow of self
    //     println!("Bumping {}", current_name);
    // }
    
    // Fix: Extract the value before mutating.
    fn bump_version_good(&mut self) {
        // Clone the string to break the borrow.
        // The clone is independent of `self`.
        let current_name = self.name.clone();
        
        // Now `self` is not borrowed.
        // Mutable access is allowed.
        self.version += 1;
        
        println!("Bumping {}", current_name);
    }
}

The error happens because &self.name creates an immutable borrow of self. The compiler sees self as borrowed immutably. Any mutation of self triggers E0502. The fix is to extract the data you need before mutating. Cloning breaks the borrow chain. If the field is Copy, like u32, you can extract it without cloning.

Extract the value. Break the reference. The borrow checker relaxes.

Closures capture borrows

Closures capture variables from their environment. If a closure captures a reference, that reference stays alive as long as the closure exists. This often causes E0502 when you try to mutate the captured value later.

fn process_data() {
    let mut buffer = vec![1, 2, 3];
    
    // Closure captures `&buffer`.
    // The borrow lasts as long as `reader` exists.
    let reader = || buffer[0];
    
    // Attempt to mutate `buffer`.
    // E0502: cannot borrow as mutable because it is also borrowed as immutable.
    // buffer.push(4);
    
    // Fix: Use the closure before mutating.
    let first = reader();
    println!("First element: {}", first);
    
    // `reader` is no longer used.
    // Mutation is allowed.
    buffer.push(4);
}

The closure holds the borrow. Even if you don't call the closure, the compiler assumes it might be called later, so the borrow persists. Use the closure early, or restructure to avoid capturing the reference.

If you need the closure to hold data independently, use a move closure. move forces the closure to take ownership of the captured variables. This can solve E0502 by removing the borrow entirely, but it requires the captured data to be owned by the closure.

Use move when the closure needs to outlive the current scope or when you need to break a borrow chain by transferring ownership.

The split_at_mut trick

Sometimes you need two mutable references to different parts of the same data. For example, swapping elements in a slice or updating two fields based on each other. Rust forbids two mutable references to the same value, but it allows two mutable references to non-overlapping parts.

The method split_at_mut creates two mutable slices from one. The compiler verifies that the split index ensures no overlap.

fn swap_first_and_last(slice: &mut [u32]) {
    if slice.len() < 2 {
        return;
    }
    
    // Split the slice into two mutable parts.
    // `first` covers index 0.
    // `rest` covers index 1 to end.
    let (first, rest) = slice.split_at_mut(1);
    
    // `rest` is now a slice starting at index 1.
    // We can get a mutable reference to the last element.
    let last = &mut rest[rest.len() - 1];
    
    // Swap the values.
    // Both `first` and `last` are mutable references.
    // This is safe because they point to different indices.
    std::mem::swap(&mut first[0], last);
}

split_at_mut is the idiomatic way to obtain multiple mutable references to a collection. It works for slices, arrays, and any type implementing SplitAtMut. The method guarantees that the resulting references do not alias.

When you need two mutable references to parts of a slice, reach for split_at_mut. It satisfies the borrow checker by proving the parts do not overlap.

Pitfalls and compiler errors

E0502 often hides behind iterators. Iterators borrow the collection. If you try to mutate the collection while the iterator is active, you get E0502.

fn remove_negatives(items: &mut Vec<i32>) {
    // This fails.
    // The iterator borrows `items` immutably.
    // `remove` requires a mutable borrow.
    // for item in items.iter() {
    //     if *item < 0 {
    //         items.remove(0); // ERROR: E0502
    //     }
    // }
    
    // Fix: Use `retain` to filter in place.
    // `retain` handles the borrow internally.
    items.retain(|&x| x >= 0);
}

Iterators hold a borrow for the duration of the loop. Mutating the collection invalidates the iterator. Use methods like retain, drain_filter, or swap_remove with indices to modify collections during traversal.

Another pitfall is holding a reference in a struct field while mutating the struct. If a struct contains a reference, the struct cannot be mutated as long as the reference is valid. This is a design constraint. Use owned data or Rc/Arc if you need shared ownership with mutation.

If you are fighting E0502 in a loop, you are probably holding a reference too long. Drop it. Use indices. Or collect the changes and apply them after the loop.

Decision matrix

Use scope blocks when the read operation finishes before the write begins; wrap the read in { } to drop the reference early and allow mutation.

Use indices when you need random access to a collection while modifying it; indices are just numbers and do not borrow the collection, so they bypass the borrow checker.

Use split_at_mut when you need two mutable references to non-overlapping parts of a slice; the method guarantees the parts do not overlap and satisfies the borrow checker.

Use clone when the data is small and you need an independent copy; this avoids borrowing entirely at the cost of allocation and copying.

Use retain or drain_filter when you need to remove items from a vector while iterating; these methods handle the borrow internally and provide a safe way to filter collections.

Use move closures when the closure needs to take ownership of captured data; this breaks borrow chains by transferring ownership to the closure.

Pick the tool that matches your access pattern. Do not force a reference where a value or index will do. The borrow checker rewards clear ownership boundaries.

Where to go next