Rust Borrow Checker Errors, Solved: value moved, cannot borrow, does not live long enough

5 min read

The perceived difficulty of early Rust mostly comes from wrestling with the borrow checker. Yet the errors you actually meet are few enough to count on one hand, and each has a standardized fix. This article is a solutions collection, organized by error code (the E numbers). If the principles are new to you, reading ownership and borrowing first is recommended.

E0382: value borrowed here after move — you used a moved value #

src/main.rs
let name = String::from("report");
let title = name;
println!("{name}"); // error[E0382]: borrow of moved value: `name`

Assigning or passing a heap-owning value is a move, and the original variable becomes invalid. There are three fixes, by situation.

  • If you only needed to read: pass a reference instead of moving. let title = &name;, or make the function parameter &str.
  • If you genuinely need two copies: let title = name.clone(); makes the copying cost explicit.
  • If a function took ownership: changing the parameter to a reference comes first; failing that, hand it back through the return value.

Moving a whole collection with for x in vec and then using it again is the same error in loop form — borrow it with for x in &vec.

E0502: immutable and mutable borrows collided #

src/main.rs
let first = &items[0];            // immutable borrow begins
items.push(99);                   // error[E0502]: cannot borrow `items` as mutable
println!("{first}");              // the immutable borrow lives until here

This is the rule “no mutation while someone is reading,” verbatim. The key fact: a borrow lives only until its last use. So the first fix is reordering.

src/main.rs
let first = items[0];  // copy the value instead of referencing (for Copy types)
items.push(99);        // fine

Either finish the reads first (move the last use before the mutation), or copy the value so there is no borrow at all. For mutation during iteration, “collect what to change, apply after the loop” is the standard form.

E0499: two mutable borrows #

src/main.rs
let a = &mut scores[0];
let b = &mut scores[1]; // error[E0499]: cannot borrow `scores` as mutable more than once

Even though the two elements are different slots, to the compiler these are two mutable borrows of the same collection — it cannot prove the indexes don’t overlap. The standard library provides a safe knife for exactly this case.

src/main.rs
let (left, right) = scores.split_at_mut(1);
let a = &mut left[0];
let b = &mut right[0]; // two non-overlapping pieces

If swapping two elements was the goal, a purpose-built method like scores.swap(0, 1) often already exists.

E0506: assigned to a value that is borrowed #

src/main.rs
let entry = &user.name;
user.name = String::from("new name"); // error[E0506]: cannot assign to `user.name`
println!("{entry}");

An attempt to replace a value wholesale while a reference to it is alive. Same principle as E0502, same fixes: move the reference’s last use before the assignment, or copy what you need instead of referencing.

E0597: borrowed value does not live long enough — the value dies before the reference #

src/main.rs
let best;
{
    let scores = vec![90, 85];
    best = &scores[0];
} // error[E0597]: `scores` does not live long enough
println!("{best}");

The referenced value goes out of scope and is freed while the reference is meant to live on. Returning a reference to a local variable from a function is the classic case in this family (together with E0106). The fix is a change of direction: don’t try to keep the reference alive longer — move ownership outward. Here, best = scores[0]; (Copy) or hoisting let scores to the outer scope; in a function, returning String rather than &String. Remembering that lifetime annotations ('a) declare relationships rather than extend lives (basics Part 9) helps you recognize when the answer is moving ownership, not adding annotations.

E0596: a closure tried to mutate a captured variable #

src/main.rs
let mut count = 0;
let inc = || count += 1; // the closure declaration itself passes
some_parallel_api(inc);  // error[E0596]: captured variable in a `Fn` closure

In sequential code, switching the closure to let mut inc = move || ... or using an API that accepts FnMut resolves it. But if this error appeared in a parallel API (rayon and friends), the story is different: multiple threads were about to mutate the same variable — a data race — and the compiler blocked it. The fix then is not closure repair but structural change: as covered in practice Part 5, build per-thread partial results and merge them (fold and reduce).

General technique #

  • Read the help line first. Half of Rust’s errors ship with a prescription attached, and rustc --explain E0382 shows the full commentary.
  • Choose the smallest tool that removes the error. clone where a reference suffices works but leaves cost behind; lifetime annotations where clone suffices work but leave complexity behind. Try in order of weight: reference → reorder → clone → structural change.
  • The borrow checker is the reviewer you meet first, not last. Most errors here are code that, in another language, would have surfaced late as runtime bugs — invalid references, mutation during iteration, data races.

Summary #

  • Use-after-move (E0382) is solved with the smallest fitting tool: reference, clone, or return. Iteration defaults to &vec.
  • Borrow conflicts (E0502, E0506) exploit “a borrow lives until its last use”: reads forward, mutations backward.
  • Two mutable borrows of one collection (E0499) call for the standard library’s safe splitting tools — split_at_mut, swap.
  • Lifetime shortfalls (E0597) are solved by moving ownership out, not by keeping references alive.
  • E0596 in a parallel closure is not a bug report but a blocked data race. Restructure to fold and reduce.
X