References and Borrowing

Common Borrowing Errors

When the borrow checker detects an invalid borrow, it reports a specific E0xxx diagnostic pointing to the creation of the first borrow, the conflicting operation, and the later use that kept the first borrow alive.

Complete Borrowing Error Reference Table

Error Code Diagnostic Message Cause Standard Fix
E0502 cannot borrow 'x' as mutable because it is also borrowed as immutable (or vice versa) Overlapping &x and &mut x lifetimes Finish reading &x before taking &mut x, copy/clone needed values upfront, or split struct fields
E0499 cannot borrow 'x' as mutable more than once at a time Two overlapping &mut x borrows Sequence the &mut borrows one after another, or use .split_at_mut(mid) on slices
E0596 cannot borrow 'x' as mutable, as it is not declared as mutable Taking &mut x when x was declared let x Change declaration to let mut x
E0594 cannot assign to '*r', which is behind a '&' reference Writing through &T Change parameter or variable type to &mut T
E0505 cannot move out of 'x' because it is borrowed Moving ownership of x while &x is still active Finish using &x before moving x
E0506 cannot assign to 'x' because it is borrowed Overwriting x = new_val while &x is still active Finish using &x before reassigning x
E0507 cannot move out of '*r' which is behind a shared/mutable reference Moving a non-Copy field/element out of &T or &mut T Borrow &r.field, .clone() it, or use std::mem::take / replace on &mut
E0515 cannot return value referencing local variable Returning &local from a function Return an owned value (String, Vec<T>) instead of a reference
E0597 'x' does not live long enough Storing &x into a reference that outlives x's block Move x to the outer block or shorten the reference's scope
E0716 temporary value dropped while borrowed Borrowing a temporary (&String::from("a").trim()) across statements Bind the temporary to a named let variable first

Walkthrough of Three Classic Borrowing Pitfalls and Fixes

1. Mutating a Collection While Holding a Reference Into It (E0502)

// ❌ Problem:
// let first = &list[0];
// list.push(4);
// println!("{first}");

fn main() {
    let mut list = vec![10, 20, 30];

    // ✅ Fix 1 (for Copy elements): Dereference/copy the value (`list[0]`) instead of borrowing `&list[0]`!
    let first: i32 = list[0];
    list.push(40);
    println!("first = {first}, list = {list:?}");
}
first = 10, list = [10, 20, 30, 40]

2. Accumulating Mutable References Inside a Loop (E0499)

If a helper function takes &'a mut State and returns a reference tied to that same lifetime &'a T that you store outside a loop, the &mut State borrow stays locked across loop iterations (E0499).

Fix: Ensure methods that only need to read State after mutating it are separated, or return owned/copied values (such as indices or cloned values) across loop iterations rather than holding long-lived borrows into &mut State.

3. Moving Out of a Borrowed Struct Field (E0507) via std::mem::take

When implementing a state transition or draining method on &mut self where you want to take ownership of a String or Vec<T> field:

struct JobQueue {
    pending: Vec<String>,
}

impl JobQueue {
    fn drain_all(&mut self) -> Vec<String> {
        // Moving `self.pending` directly would fail with `E0507`!
        // `std::mem::take` swaps in an empty `Vec::new()` and returns the owned `Vec<String>`:
        std::mem::take(&mut self.pending)
    }
}

fn main() {
    let mut queue = JobQueue {
        pending: vec![String::from("job_1"), String::from("job_2")],
    };

    let jobs = queue.drain_all();
    println!("Drained: {jobs:?}, remaining in queue: {:?}", queue.pending);
}
Drained: ["job_1", "job_2"], remaining in queue: []