Rust Borrow Checker Fixer
Systematically diagnose and resolve borrow checker and lifetime errors. Don't guess — trace the actual ownership and borrowing relationships.
When Invoked
The user has a Rust compilation error related to borrowing, lifetimes, or ownership. They will provide compiler output and/or code.
Diagnosis Process
Step 1: Classify the Error
Identify which category the error falls into:
| Error Pattern | Category | Typical Cause |
|---|---|---|
cannot borrow X as mutable because it is also borrowed as immutable | Aliasing violation | Overlapping & and &mut to same data |
X does not live long enough | Lifetime too short | Borrowed data dropped before borrow ends |
cannot move out of X because it is borrowed | Move-while-borrowed | Trying to take ownership while borrow exists |
lifetime may not live long enough | Lifetime mismatch | Function signature lifetimes don't match body |
missing lifetime specifier | Elision failure | Compiler can't infer lifetimes, needs annotation |
cannot return reference to local variable | Dangling reference | Returning a borrow to stack data |
closure may outlive the current function | Capture lifetime | Closure captures a borrow that doesn't live long enough |
use of moved value | Use-after-move | Value consumed, then used again |
Step 2: Trace the Data Flow
For the problematic variable/reference:
- •Where is it created? (owned, borrowed, or received as parameter)
- •Where is it borrowed? (each
&and&mut) - •Where does each borrow end? (last use of the reference)
- •Where is it moved or dropped? (ownership transfer or scope end)
- •Do any borrows overlap in incompatible ways?
Draw this out mentally or in comments before attempting a fix.
Step 3: Choose a Fix Strategy
Prefer fixes in this order (least invasive to most):
- •
Reorder statements — Often the simplest fix. Move the mutable borrow after the last use of the immutable borrow.
- •
Narrow borrow scope — Introduce a block
{ }to limit how long a borrow lives.rust// Before: borrows overlap let x = &data; let y = &mut data; // ERROR // After: first borrow ends before second { let x = &data; use(x); } let y = &mut data; // OK - •
Split the struct — If you're borrowing two fields of the same struct, borrow them individually instead of borrowing
&mut self.rust// Before: can't borrow self twice self.process(self.data); // ERROR // After: borrow fields independently let data = &self.data; Self::process_data(data, &mut self.output);
- •
Change function signature — Accept
&selfinstead of&mut selfif mutation isn't needed, or take ownership if the caller doesn't need the value anymore. - •
Use interior mutability —
Cell,RefCell,Mutexwhen shared mutation is genuinely needed (not as a borrow-checker escape hatch). - •
Restructure ownership — Sometimes the borrow checker is telling you the design is wrong. If data needs to be shared and mutated by multiple owners, rethink who owns what.
Avoid these "fixes":
- •Adding
.clone()without understanding why — this hides the real issue - •Adding
'staticlifetime — this almost never solves the actual problem - •Using
unsafeto bypass the borrow checker — this is always wrong - •Wrapping everything in
Rc<RefCell<T>>— this defeats Rust's compile-time guarantees
Step 4: Verify the Fix
After applying the fix:
- •Does it compile? Run
cargo check. - •Does it preserve the original intent? (No accidental copies of large data, no changed semantics)
- •Is it the minimal change? Don't restructure the whole module for a one-line fix.
- •Are there other similar patterns in the file that need the same fix?
Common Patterns and Solutions
"Temporary value dropped while borrowed"
// BAD: temporary String dropped, but we borrowed a &str from it
let s: &str = &format!("hello {}", name);
// GOOD: bind the temporary to extend its lifetime
let owned = format!("hello {}", name);
let s: &str = &owned;
"Cannot borrow as mutable more than once"
// BAD: two mutable borrows let a = &mut vec[0]; let b = &mut vec[1]; // ERROR even though different indices // GOOD: use split_at_mut or index once let (left, right) = vec.split_at_mut(1); let a = &mut left[0]; let b = &mut right[0];
"Closure may outlive current function"
// BAD: closure captures &local let local = String::new(); thread::spawn(|| use_string(&local)); // ERROR // GOOD: move ownership into closure let local = String::new(); thread::spawn(move || use_string(&local)); // OK, closure owns it
Output Format
- •Error classification — which category and why
- •Data flow trace — where the problematic borrow originates and conflicts
- •Recommended fix — with code, using the least invasive approach
- •Why this fix is correct — not just "it compiles" but why the ownership is sound