What this usually means
The Rust compiler enforces that every reference must be valid for the entire time it is used. When you see 'does not live long enough', it means the data behind the reference is dropped before the reference goes out of scope. This often happens when you try to return a reference to a local variable, store a reference to a temporary, or capture a reference in a closure that outlives the borrowed value. The tricky part is that the error can also arise from complex lifetime constraints, like when multiple lifetimes interact through generic bounds or associated types. In async code, the compiler's view of lifetimes can be even stricter because futures can be polled across yield points, and the borrow checker may not see that the data is still valid. Understanding where the data lives and how long it lives is key—sometimes you need to own the data, sometimes you need to adjust lifetimes with explicit annotations, and sometimes you need to restructure your code to avoid borrowing altogether.
The first ten minutes — establish facts before touching code.
- 1Read the full error message: compile with `cargo build` and note the line numbers and lifetime annotations mentioned.
- 2Identify the dropped value: the error points to a specific variable—check where it is defined and where it is last used.
- 3Trace the reference lifetime: look at the function signature—if it returns a reference, ensure the input lifetime matches the output.
- 4Check for temporary scopes: if the error involves a temporary like `&foo()` or a field of a temporary, evaluate the temporary's scope.
- 5Run `rustc --explain E0597` for a detailed explanation of the error code.
- 6If in async code, inspect the `async` block: use `async move` if the reference is to owned data, or clone the data.
The specific files, logs, configs, and dashboards that usually own this bug.
- searchThe function or method signature where the reference is returned.
- searchThe variable's scope—local variables in a block or function body.
- searchLifetime annotations on struct fields that hold references.
- searchAsync block or closure captures—check for `&` vs `move`.
- searchGeneric lifetime parameters in trait bounds or impl blocks.
- searchThe `drop` order of fields in a struct if one field borrows another.
- searchThird-party crate's API that returns references tied to internal data.
Practical causes, not theory. These are the things you will actually find.
- warningReturning a reference to a local variable created inside the function.
- warningStoring a reference to a temporary that goes out of scope at the end of the statement.
- warningCapturing a reference in a closure or async block that outlives the borrowed data.
- warningMismatched lifetime annotations where the output lifetime is not constrained by the input.
- warningStruct with a reference field that outlives the data it points to (e.g., self-referential struct).
- warningUsing `&` on a value inside a loop that gets dropped at the end of the iteration.
- warningAsync function that returns a reference to a local because the lifetime is not `'static`.
Concrete fix directions. Pick the one that matches your root cause.
- buildOwn the data instead of borrowing: return `String` instead of `&str`, or clone the reference.
- buildAdjust lifetimes: add explicit lifetime annotations like `<'a>` and tie output to input lifetimes.
- buildUse `Box::new` with `Box::leak` to create a static reference if the data must outlive the function.
- buildChange to `async move` to move owned data into the async block and avoid borrowing.
- buildRefactor to avoid self-referential structs: use `Pin<Box<Self>>` or use indices instead of references.
- buildUse `Rc` or `Arc` with `RefCell`/`Mutex` for shared ownership with interior mutability.
- buildExtract the borrowed value into a new scope that matches the reference's lifetime.
A fix you cannot prove is a guess. Close the loop.
- verifiedCompile with `cargo check` after each fix—no lifetime errors.
- verifiedRun `cargo clippy` to catch common lifetime anti-patterns.
- verifiedWrite a test that exercises the edge case (e.g., multiple calls to the function that returns a reference).
- verifiedIf using unsafe, run with `cargo test` under Miri to detect undefined behavior.
- verifiedFor async code, run with `cargo test` and ensure no 'borrowed value does not live long enough' panics.
- verifiedReview the function signature to confirm the lifetime constraints are correct.
- verifiedAdd `#[deny(elided_lifetimes_in_paths)]` to force explicit lifetimes and catch mismatches.
Things that make this bug worse or harder to find.
- warningAdding `'static` without thinking: not every reference should be static; it can hide real ownership issues.
- warningIgnoring the 'temporary dropped' part: check for `&foo()` where `foo` returns a value.
- warningUsing `unsafe` to silence the error: the compiler is right; unsafe will likely cause use-after-free.
- warningOvercomplicating lifetime annotations: sometimes the fix is to just own the data or use `Cow`.
- warningForgetting that closures capture by reference by default: use `move` if the closure outlives the capture scope.
- warningAssuming async blocks are like synchronous blocks: the compiler is stricter because futures can be polled later.
Async WebSocket Handler: 'does not live long enough' in a tokio::spawned future
Timeline
- 09:00Deploy new WebSocket endpoint that queries DB and streams results.
- 09:05Production builds fail with E0597 on a reference in an async block.
- 09:10Read error: borrowed value `db_pool` does not live long enough inside `tokio::spawn`.
- 09:15Check the code: `tokio::spawn(async { ... &db_pool ... })` – db_pool is a function parameter.
- 09:20Realize db_pool is a reference to a local pool created in the handler.
- 09:25Fix: change `db_pool` to an owned `Pool` passed via `Arc` or clone the pool for the task.
- 09:30Build passes on staging; deploy to prod.
- 09:35Monitoring shows no regressions; WebSocket works as expected.
I was building a real-time dashboard that pushed database updates over WebSocket. The handler received a connection and a database pool reference, then spawned a background task to listen for notifications. The code looked clean: `tokio::spawn(async { ... let row = sqlx::query(...).fetch_one(&db_pool).await ... })`. But the compiler screamed 'does not live long enough' on `db_pool`.
At first I thought it was a false positive—I knew the pool outlived the task because the handler owned it. But the compiler was right: `db_pool` was a reference to a local variable in the handler function. When the handler returned, the pool was dropped, but the spawned task could outlive the handler. The reference became dangling.
I fixed it by making the pool owned inside the async block. I used `Arc<Pool>` shared across tasks, or simply cloned the pool (since `Pool` is internally reference-counted). After the fix, the compilation succeeded, and the WebSocket endpoint ran without issues. The lesson: async + tokio::spawn requires `'static` bounds; you cannot borrow local data into a spawned future.
Root cause
The tokio::spawned future captured a reference `&db_pool` that pointed to a local variable in the handler function. The future outlived the handler, so the reference became invalid.
The fix
Changed the handler to accept an owned `Pool` (e.g., `Arc<Pool>`) and used `async move` to move the owned pool into the spawned task.
The lesson
When using `tokio::spawn`, the future must own all its data or use `'static` references. Prefer `async move` with owned values to avoid lifetime issues.
When you write a function that returns a reference, Rust requires you to specify how the output's lifetime relates to the input lifetimes. For example, `fn first<'a>(x: &'a str, y: &str) -> &'a str` tells the compiler that the returned reference lives at most as long as `x`. Without this annotation, the compiler cannot infer the relationship and will complain about 'does not live long enough'.
A common mistake is forgetting to annotate when the output is a reference to a field of a struct. For instance, if you have a struct `Container<'a> { data: &'a str }` and a method `fn get(&self) -> &str { self.data }`, Rust elides the lifetime to `fn get<'a>(&'a self) -> &'a str`, which is correct. But if you have a method that returns a reference to an internal field that is not part of the self lifetime, you need explicit annotations.
A self-referential struct is one where a field holds a reference to another field in the same struct. Rust's borrow checker normally forbids this because moving the struct would invalidate the reference. However, sometimes it appears innocuous: `struct S { data: String, slice: &'self [u8] }` (not valid syntax). The compiler will reject this because when you move `S`, `data` moves and the reference `slice` becomes dangling.
To work around this, you need to use `Pin` and unsafe code, or restructure to avoid self-references. Libraries like `ouroboros` or `self_cell` can help, but the simplest fix is often to store indices instead of references. For instance, instead of `slice: &'self [u8]`, store `offset: usize, length: usize` and compute the slice on demand.
Rust's lifetime elision rules make common patterns concise: `fn foo(x: &str) -> &str` is automatically expanded to `fn foo<'a>(x: &'a str) -> &'a str`. This works great for simple functions. But when you have multiple input references, elision can produce unexpected lifetimes. For example, `fn bar(x: &str, y: &str) -> &str` is rejected because the compiler cannot decide which input's lifetime to use. You must annotate explicitly.
In methods, elision uses `&self`'s lifetime for all output references. This can lead to 'does not live long enough' if you want to return a reference that is independent of `&self`. For example, a method that returns a reference to a static string will fail unless you annotate the output lifetime as `'static`. Always be explicit when the output lifetime differs from the default.
When a reference doesn't live long enough, the simplest fix is to stop borrowing and start owning. Instead of `&str`, return `String`. Instead of `&[u8]`, return `Vec<u8>`. This eliminates lifetime issues entirely, at the cost of a heap allocation. For performance-critical code, consider `Cow<'a, str>` to borrow when possible and own when necessary.
For shared ownership across threads, use `Arc<T>`. If you need a `&'static T`, you can leak a `Box` with `Box::leak`. This is appropriate for configuration that lives for the program's lifetime. But be careful: leaking memory is acceptable only for truly static data. For temporary but long-lived data, consider using a `once_cell` or `lazy_static` pattern.
Frequently asked questions
I see 'does not live long enough' in a simple function returning a reference. What am I missing?
You likely have a local variable whose reference you are returning. For example: `fn get_ref() -> &i32 { let x = 42; &x }`. The variable `x` is dropped when the function returns, so the reference is invalid. The fix is to either return an owned value (e.g., `i32`) or have the reference point to data that lives longer (e.g., a static or an input parameter).
How do I fix lifetime errors in async code without switching to owned types?
If you must use references, ensure the referenced data has a `'static` lifetime or use `async move` with `Arc`. For example, share your database pool via `Arc<Pool>` and clone the `Arc` into the async block. Alternatively, use `tokio::task::spawn` with `'static` bounds: the future must own all data. Avoid borrowing from the enclosing scope.
Why does my struct with a reference field cause 'does not live long enough' when I try to return it from a function?
This happens when the struct is created inside the function and its reference field points to a local variable. When the function returns, the local is dropped, but the struct's reference field still points to it. To fix, ensure the referenced data lives as long as the struct: either take the reference as a parameter (with the same lifetime) or own the data (e.g., `String` instead of `&str`).
What is the difference between `&'a T` and `&'static T` in the context of 'does not live long enough'?
`&'a T` is a reference that is valid for lifetime `'a`, which is usually tied to some scope (e.g., a function parameter). `&'static T` is a reference that is valid for the entire program. If you need a reference that outlives the current scope, you may need `'static`. However, forcing `'static` on a non-static reference will cause the error. The fix is to ensure the data is actually static (e.g., a `const` or leaked box).
My code uses a closure that captures a reference to an iterator. Why does the compiler say 'does not live long enough'?
Closures capture references by default. If the closure is returned or stored, the captured reference must outlive the closure. In the case of an iterator, the iterator is often a local temporary that is consumed. Try making the iterator owned (e.g., `into_iter()`) and use `move` on the closure to move the owned iterator into the closure.