---
# === IDENTITY ===
id: software/debugging/rust-borrow-checker/2026
canonical_question: "How do I fix common Rust borrow checker errors?"
aliases:
  - "Rust borrow checker errors and fixes"
  - "How to resolve Rust ownership and borrowing errors"
  - "Rust E0382 E0502 E0505 E0597 fix guide"
  - "Fighting the Rust borrow checker"
entity_type: software_reference
domain: software > debugging > rust_borrow_checker
region: global
jurisdiction: global
temporal_scope: 2020-2026

# === VERIFICATION ===
last_verified: 2026-02-23
confidence: 0.93
version: 1.0
first_published: 2026-02-23

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Rust 1.63 (2022-08-11) — NLL enabled by default on all editions"
  next_review: 2026-08-22
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "At any given time, you can have EITHER one mutable reference OR any number of immutable references to the same data — never both"
  - "References must always be valid — no dangling references allowed (enforced at compile time)"
  - "Values cannot be used after ownership is moved (transferred) to another binding"
  - "Mutable references require exclusive access — no other references (mutable or immutable) can coexist"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Error is a lifetime annotation issue on function signatures or struct definitions (E0106, E0621)"
    use_instead: "Rust lifetime annotations guide"
  - condition: "Error involves async/await and Pin<Box<dyn Future>> (E0277 with Future trait bounds)"
    use_instead: "Rust async ownership patterns guide"
  - condition: "Error is about trait object safety or dyn dispatch (E0038)"
    use_instead: "Rust trait objects and dispatch guide"

# === AGENT HINTS ===
inputs_needed:
  - key: "error_code"
    question: "What is the exact Rust compiler error code (e.g., E0382, E0502)?"
    type: choice
    options: ["E0382", "E0499", "E0502", "E0505", "E0515", "E0597", "E0716", "other"]
  - key: "context"
    question: "Where does the error occur?"
    type: choice
    options: ["loop/iterator", "function return", "struct method", "closure/async", "match/if-let"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/debugging/rust-borrow-checker/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-23)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to: []
  solves: []
  alternative_to: []
  often_confused_with: []

# === SOURCES (7 authoritative sources) ===
sources:
  - id: src1
    title: "References and Borrowing — The Rust Programming Language"
    author: The Rust Project
    url: https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html
    type: official_docs
    published: 2024-01-01
    reliability: authoritative
  - id: src2
    title: "Fixing Ownership Errors — The Rust Programming Language (Brown University Edition)"
    author: Brown University / Will Crichton
    url: https://rust-book.cs.brown.edu/ch04-03-fixing-ownership-errors.html
    type: official_docs
    published: 2024-06-01
    reliability: high
  - id: src3
    title: "Validating References with Lifetimes — The Rust Programming Language"
    author: The Rust Project
    url: https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html
    type: official_docs
    published: 2024-01-01
    reliability: authoritative
  - id: src4
    title: "Item 15: Understand the borrow checker — Effective Rust"
    author: David Drysdale
    url: https://www.lurklurk.org/effective-rust/borrows.html
    type: technical_blog
    published: 2024-03-01
    reliability: high
  - id: src5
    title: "Rust Error Codes Index — E0382"
    author: The Rust Project
    url: https://doc.rust-lang.org/error_codes/E0382.html
    type: official_docs
    published: 2024-01-01
    reliability: authoritative
  - id: src6
    title: "Non-lexical lifetimes (NLL) fully stable — Rust Blog"
    author: Jack Huey / Rust Project
    url: https://blog.rust-lang.org/2022/08/05/nll-by-default/
    type: official_docs
    published: 2022-08-05
    reliability: authoritative
  - id: src7
    title: "Tips and tricks for dealing with the borrow-checker"
    author: jyn514
    url: https://gist.github.com/jyn514/638ab0bcf2924c8286e78f38a7f5bdee
    type: community_resource
    published: 2023-01-15
    reliability: moderate_high
---

# Fixing Common Rust Borrow Checker Errors

## TL;DR

- **Bottom line**: Most borrow checker errors fall into 7 patterns (E0382, E0499, E0502, E0505, E0515, E0597, E0716) — each has a specific fix involving cloning, borrowing, restructuring scopes, or returning owned values.
- **Key tool/command**: `cargo check` (fast compile check without codegen) and `rustc --explain E0XXX` for detailed error explanations.
- **Watch out for**: Reaching for `.clone()` on everything — it compiles but hides design problems; prefer restructuring borrows first.
- **Works with**: Rust 1.31+ (NLL on 2018 edition), Rust 1.63+ (NLL on all editions). Stable through Rust 1.85 (Feb 2026).

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- You can have EITHER one `&mut T` OR any number of `&T` to the same data at a time — never both simultaneously
- A value cannot be used after its ownership moves to another variable or function call
- References must not outlive the data they point to — the compiler enforces this at every function boundary
- `unsafe` blocks can bypass the borrow checker but shift responsibility for memory safety to the programmer — never suggest `unsafe` as a fix for borrow checker errors
- Interior mutability types (`Cell`, `RefCell`, `Mutex`) move borrow checking to runtime — panics replace compile errors if misused

## Quick Reference

| # | Error Code | Cause | Likelihood | Signature | Fix |
|---|---|---|---|---|---|
| 1 | E0382 | Use of moved value | ~30% of cases | `value used here after move` | Use `&val` (borrow), `.clone()`, or restructure to avoid the move |
| 2 | E0502 | Mutable borrow while immutable borrow is active | ~20% of cases | `cannot borrow as mutable because it is also borrowed as immutable` | End the immutable borrow before mutating; use a separate scope or reorder statements |
| 3 | E0505 | Move out of borrowed value | ~12% of cases | `cannot move out of because it is borrowed` | Clone the value, or drop the borrow first |
| 4 | E0597 | Value does not live long enough | ~12% of cases | `does not live long enough` | Extend the value's scope, return owned data instead of references, or use lifetime annotations |
| 5 | E0499 | Multiple mutable borrows | ~10% of cases | `cannot borrow as mutable more than once at a time` | Use `split_at_mut()`, separate scopes, or `Cell`/`RefCell` |
| 6 | E0515 | Return reference to local variable | ~6% of cases | `cannot return reference to local variable` | Return the owned value instead of a reference |
| 7 | E0716 | Temporary value dropped while borrowed | ~5% of cases | `temporary value dropped while borrowed` | Bind the temporary to a named variable with `let` |
| 8 | E0507 | Cannot move out of borrowed content | ~3% of cases | `cannot move out of behind a shared reference` | Use `.clone()`, `std::mem::replace()`, or match on `ref` |
| 9 | E0596 | Cannot borrow immutable as mutable | ~2% of cases | `cannot borrow as mutable, as it is not declared as mutable` | Add `mut` to the binding: `let mut x = ...` |

## Decision Tree

> Full script: [decision-tree.txt](scripts/decision-tree.txt) (26 lines)

```
START — What does the error message say?
├── "value used here after move" (E0382)?
│   ├── Value is Copy type (i32, f64, bool, char) → Should not happen. Check for shadowing.
│   ├── Value used once after move → Borrow with & instead of moving.
│   ├── Value used in loop → Clone before the loop or use &val in iteration.
# ... (see full script)
```

## Step-by-Step Guide

### 1. Read the full error message

Rust's compiler errors are among the best in any language. Read the entire output — it often includes a suggested fix. [src1]

```bash
# Run a quick compile check (no codegen — faster than cargo build)
cargo check 2>&1
```

**Verify**: Look for lines starting with `help:` in the compiler output — these contain suggested fixes.

### 2. Identify the error code and look it up

Every borrow checker error has an error code (E0XXX). Use the built-in explain command to understand it. [src5]

```bash
# Get a detailed explanation with examples
rustc --explain E0382
```

**Verify**: The explanation includes a minimal example of what triggers the error and how to fix it.

### 3. Locate the conflicting borrows or moves

The compiler highlights the exact lines involved. Look for the "first borrow/move occurs here" and "second borrow/use occurs here" annotations. [src2]

```rust
// Example: the compiler shows exactly where the conflict is
fn main() {
    let mut data = vec![1, 2, 3];
    let first = &data[0];   // ── immutable borrow occurs here
    data.push(4);            // ── mutable borrow occurs here
    println!("{}", first);   // ── immutable borrow later used here
}
// Fix: finish using `first` before calling data.push()
```

**Verify**: `cargo check` compiles without errors after restructuring.

### 4. Apply the appropriate fix pattern

Based on the error code, apply the correct fix from the Quick Reference table. The most common fixes are: [src4]

```rust
// Fix pattern: Reorder to end borrow before mutation
fn main() {
    let mut data = vec![1, 2, 3];
    let first = data[0];     // Copy the value (i32 is Copy)
    data.push(4);             // Now safe: no outstanding borrows
    println!("{}", first);    // Uses the copied value
}
```

**Verify**: `cargo check` → no errors. Then `cargo test` to confirm behavior.

### 5. Run Clippy for additional suggestions

Clippy catches patterns that compile but are suboptimal (e.g., unnecessary clones). [src7]

```bash
# Run Clippy for lint-level suggestions
cargo clippy -- -W clippy::all
```

**Verify**: `cargo clippy` returns no warnings related to borrowing or cloning.

## Code Examples

### Rust: Fixing E0382 — Use of Moved Value

```rust
// Input:  Code that moves a String then tries to use it
// Output: Compiling code using borrowing instead of moving

// BEFORE (fails to compile — E0382):
// fn greet(name: String) { println!("Hello, {}!", name); }
// let user = String::from("Alice");
// greet(user);
// println!("Goodbye, {}!", user);  // ERROR: value used after move

// AFTER (compiles — borrows instead of moving):
fn greet(name: &str) {
    println!("Hello, {}!", name);
}

fn main() {
    let user = String::from("Alice");
    greet(&user);                      // Borrow: user is not moved
    println!("Goodbye, {}!", user);    // OK: user is still owned here
}
```

### Rust: Fixing E0502 — Immutable and Mutable Borrow Conflict

```rust
// Input:  Code that reads and writes to a Vec simultaneously
// Output: Compiling code with borrows properly sequenced

fn main() {
    let mut scores = vec![90, 85, 78, 92];

    // BEFORE (E0502): immutable borrow overlaps with mutable borrow
    // let highest = scores.iter().max().unwrap();
    // scores.push(95);
    // println!("Was: {}", highest);

    // AFTER: clone the result so the immutable borrow ends
    let highest = *scores.iter().max().unwrap(); // Copy the i32 value
    scores.push(95);
    println!("Was: {}", highest); // Uses the copied value, not a borrow
}
```

### Rust: Fixing E0505 — Cannot Move While Borrowed

```rust
// Input:  Code that tries to move a value while a reference exists
// Output: Compiling code with the borrow scoped correctly

fn main() {
    let mut data = String::from("hello");

    // BEFORE (E0505):
    // let r = &data;
    // let moved = data;  // ERROR: cannot move while borrowed
    // println!("{}", r);

    // AFTER: limit the borrow's scope
    {
        let r = &data;
        println!("{}", r);   // Borrow used and dropped here
    }
    let moved = data;         // Move is now safe
    println!("{}", moved);
}
```

### Rust: Fixing E0597 — Value Does Not Live Long Enough

```rust
// Input:  Function returning a reference to a local value
// Output: Function returning an owned value instead

// BEFORE (E0597):
// fn make_greeting(name: &str) -> &str {
//     let greeting = format!("Hello, {}!", name);
//     &greeting  // ERROR: `greeting` does not live long enough
// }

// AFTER: return owned String instead of &str
fn make_greeting(name: &str) -> String {
    format!("Hello, {}!", name)  // Ownership transfers to caller
}

fn main() {
    let msg = make_greeting("Alice");
    println!("{}", msg);
}
```

## Anti-Patterns

### Wrong: Cloning everything to silence the borrow checker

```rust
// BAD — .clone() compiles but wastes memory and hides design issues
fn process(items: &Vec<String>) {
    let owned_copy = items.clone(); // Unnecessary deep clone
    for item in owned_copy.iter() {
        println!("{}", item);
    }
}
```

### Correct: Borrow instead of clone

```rust
// GOOD — zero-cost borrow, no allocation
fn process(items: &[String]) {   // Accept a slice, not &Vec
    for item in items.iter() {
        println!("{}", item);
    }
}
```

### Wrong: Using indices to avoid borrow checker instead of split borrows

```rust
// BAD — bypasses borrow checker with indices, prone to out-of-bounds bugs
fn swap_first_last(v: &mut Vec<i32>) {
    let first = v[0];
    let last = v[v.len() - 1];
    v[0] = last;
    v[v.len() - 1] = first;
}
```

### Correct: Use the standard library's safe swap

```rust
// GOOD — safe, idiomatic, handles edge cases
fn swap_first_last(v: &mut Vec<i32>) {
    let len = v.len();
    if len >= 2 {
        v.swap(0, len - 1); // Built-in safe swap
    }
}
```

### Wrong: Using RefCell everywhere to avoid compile-time borrow checking

```rust
// BAD — moves borrow checking to runtime; panics if rules violated
use std::cell::RefCell;

fn main() {
    let data = RefCell::new(vec![1, 2, 3]);
    let borrow1 = data.borrow();
    let borrow2 = data.borrow_mut(); // PANIC at runtime!
    println!("{:?} {:?}", borrow1, borrow2);
}
```

### Correct: Restructure code to satisfy compile-time borrow checking

```rust
// GOOD — compile-time guarantee, no runtime panics
fn main() {
    let mut data = vec![1, 2, 3];
    // Read phase
    let sum: i32 = data.iter().sum();
    println!("Sum: {}", sum);
    // Write phase (no outstanding borrows)
    data.push(4);
    println!("{:?}", data);
}
```

### Wrong: Using unsafe to bypass the borrow checker

```rust
// BAD — undefined behavior waiting to happen
fn get_two_mut(v: &mut Vec<i32>, i: usize, j: usize) -> (&mut i32, &mut i32) {
    unsafe {
        let ptr = v.as_mut_ptr();
        (&mut *ptr.add(i), &mut *ptr.add(j)) // UB if i == j
    }
}
```

### Correct: Use split_at_mut for safe disjoint mutable borrows

```rust
// GOOD — safe, panics on overlapping indices instead of UB
fn get_two_mut(v: &mut [i32], i: usize, j: usize) -> (&mut i32, &mut i32) {
    assert!(i != j, "indices must be different");
    if i < j {
        let (left, right) = v.split_at_mut(j);
        (&mut left[i], &mut right[0])
    } else {
        let (left, right) = v.split_at_mut(i);
        (&mut right[0], &mut left[j])
    }
}
```

## Common Pitfalls

- **Borrowing through a method call hides the borrow**: Calling `self.field.method()` borrows all of `self`, not just `field`. Fix: extract the field into a local variable first — `let f = &mut self.field; f.method();`. [src4]
- **Iterating and mutating the same collection**: `for item in &vec { vec.push(...) }` borrows `vec` immutably for the entire loop. Fix: collect indices or values first, then mutate in a separate pass. [src1]
- **Returning references to local variables**: Functions cannot return references to stack-allocated locals because they are dropped at function exit. Fix: return owned types (`String`, `Vec<T>`, `Box<T>`) or use lifetime parameters to tie return references to input references. [src3]
- **Closures capture more than expected**: A closure that uses `self.field` captures all of `self`. Fix: bind `let field = &self.field;` before the closure, or use the `{let field = &self.field; move || ... }` pattern. [src7]
- **Temporary values dropped too early**: `let r = &some_fn().field;` — the temporary returned by `some_fn()` is dropped at the semicolon. Fix: `let tmp = some_fn(); let r = &tmp.field;`. [src2]
- **Confusing moves with copies**: Types not implementing `Copy` (like `String`, `Vec`) are moved by default. Beginners expect assignment to copy. Fix: use `.clone()` when you genuinely need a copy, or borrow with `&`. [src5]
- **HashMap entry API ignored**: `if !map.contains_key(&k) { map.insert(k, v); }` borrows `map` twice. Fix: use `map.entry(k).or_insert(v);` — a single borrow. [src4]
- **String vs &str confusion**: Passing `String` where `&str` suffices triggers unnecessary moves. Fix: accept `&str` in function signatures; Rust auto-derefs `&String` to `&str`. [src1]

## Diagnostic Commands

```bash
# Quick compile check (no codegen — fastest feedback loop)
cargo check

# Detailed explanation for any error code
rustc --explain E0382

# Run Clippy lints (catches unnecessary clones, borrow issues)
cargo clippy -- -W clippy::all

# Run Clippy with pedantic lints for deeper analysis
cargo clippy -- -W clippy::pedantic

# Show full error output with backtrace
RUST_BACKTRACE=1 cargo check

# Check specific file without full project build
rustc --edition 2021 --crate-type lib src/module.rs 2>&1

# Use cargo expand to see macro-generated code (borrow issues in macros)
cargo expand --lib module_name

# Test with Miri for undefined behavior (nightly only)
cargo +nightly miri test
```

## Version History & Compatibility

| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| Rust 1.85 (Feb 2026) | Current stable | None for borrowck | — |
| Rust 1.82 (2024 Edition) | Stable | Temporary lifetime changes in tail expressions and `if let` | Some patterns with temporaries in match arms may need `let` bindings |
| Rust 1.63 (Aug 2022) | NLL default all editions | NLL enabled for 2015 edition | Rare: some unsound code that compiled before now rejected |
| Rust 1.36 (Jul 2019) | NLL for 2018 edition | Non-lexical lifetimes enabled | References end at last use, not end of scope — strictly more permissive |
| Rust 1.31 (Dec 2018) | 2018 Edition | NLL introduced (2018 edition only) | Opt-in via `edition = "2018"` in Cargo.toml |
| Polonius (nightly) | Experimental | More permissive borrow checking | `-Zpolonius` flag; resolves some false positives (e.g., conditional returns in loops) |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| You get any E0382/E0499/E0502/E0505/E0597/E0515/E0716 error | The error is about trait bounds (E0277) | Rust trait bounds / generics guide |
| You need to restructure code to satisfy ownership rules | The error is about lifetime annotations on function signatures | Rust lifetime annotations guide |
| You want to understand why Rust rejects your code | You are writing `unsafe` code intentionally | Rustonomicon (unsafe Rust reference) |
| You need to share data across threads | You need async-specific patterns (Pin, Future) | Rust async/await ownership guide |

## Important Caveats

- NLL (non-lexical lifetimes) is standard since Rust 1.63 for all editions, but older blog posts and Stack Overflow answers may reference pre-NLL behavior where borrows lasted until end-of-scope — those fixes may be unnecessarily restrictive
- Polonius (next-gen borrow checker) is experimental on nightly (`-Zpolonius`) and resolves some false positives, particularly conditional borrows in loops — it is not yet stable
- The 2024 edition (Rust 1.82+) changes temporary lifetime rules in tail expressions, which may introduce new E0716 errors in code that compiled on 2021 edition
- `RefCell<T>` and `Mutex<T>` defer borrow checking to runtime — they are valid tools but should not be the first resort for every borrow checker error
- IDE borrow checker analysis (rust-analyzer) may occasionally lag behind the actual compiler; always verify with `cargo check`

## Related Units
<!-- Generated from related_kos frontmatter -->

- Rust lifetime annotations guide (planned)
- Rust async ownership patterns (planned)
- Rust smart pointers (Box, Rc, Arc) guide (planned)
