← Debugging Guides

阅读中文版本

Rust — error[E0373]: closure may outlive the current function

Fix E0373 closure may outlive the current function by understanding borrowed captures versus move semantics, and pass ownership or clone into long-lived closures.

intermediatedebuggingrustclosuresownership

Symptoms

  • error[E0373]: closure may outlive the current function, but it borrows x with a note about the borrowed variable
  • The closure is passed to spawn, a framework handler, or stored in a struct that outlives the call
  • Adding move compiles but shifts the error to a borrow of a moved value elsewhere

Possible causes

  • A closure passed to thread::spawn or stored long-term borrows local variables by reference
  • The compiler suggests adding the move keyword, but moving conflicts with later uses in the same function
  • A framework stores the closure in a registry, so its captures must be owned, not borrowed from stack locals
  • Shared references intended for reading need lifetime extension via Rc/Arc clones instead of move

Diagnosis steps

Read the borrow note

cargo check 2>&1 | grep -B2 -A10 E0373

Interpretation

The note names the borrowed variable and where the closure escapes — the exact pair of lifetime ends that conflict.

Decide ownership versus sharing

cargo check 2>&1 | grep "consider adding" | head -3

Interpretation

move works when the function no longer needs the value; Arc/Rc clones work when both the closure and the caller need access.

Verify both users compile

cargo check && cargo test

Interpretation

After move or clone, the closure owns its captures and the original scope keeps what it needs — the test suite proves semantics survived.

Root cause

A closure captures variables by reference by default, tying its lifetime to the borrowed locals. When the closure escapes the function — into a thread, a handler registry, a struct field — those locals would be gone while the closure still references them, so the compiler demands either ownership (move) or shared ownership (clone into Arc/Rc).

Fix

Add move when the function can give up ownership, clone small values into the closure when the caller still needs them, use Arc for shared state captured by long-lived closures, and restructure APIs that ask for closures to accept owned data plus function pointers where appropriate.

Risk note

move changes Drop timing and can surprise code that relies on the original binding after the closure. Review uses after the move point.

Verify the fix

The escaping closure compiles; tests confirm the closure executes correctly after the original function returned, and the caller-side uses still work.

Symptom walkthrough

The error names the escape and the borrow in one note:

error[E0373]: closure may outlive the current function,
               but it borrows `config`
 --> src/worker.rs:12:24
  |
12 |     let task = spawn(move || use(&config));
   |                    ------- ^- `config` is borrowed here
  |                    |
  |                    this closure may outlive the current function

The conflict is between two lifetimes: the closure is stored somewhere that outlives this call (spawn, a handler table, a struct), while its capture borrows a local that dies at function return. The compiler suggests move; the subtlety is that move changes what the caller can still do with the value afterward — sometimes the right fix is a clone or an Arc instead.

Fix in practice

Three shapes by need. Caller done with the value — plain move:

let task = thread::spawn(move || process(config)); // ownership travels

Caller still needs it — clone in:

let snapshot = config.clone();
let task = thread::spawn(move || process(snapshot));
use(&config); // caller keeps its copy

Both need it long-term — shared ownership:

let shared = Arc::new(config);
let for_task = Arc::clone(&shared);
let task = thread::spawn(move || process(for_task));
use(&*shared);

For framework handler registrations where the closure lives as long as the app, treat owned captures as the design default: everything the closure needs is either moved in, cloned in, or shared via Arc — never borrowed from a request-scoped stack.