Quick answer
A reference outlived the value it points to. The value is dropped (its memory freed) at the end of its own scope, but something is still trying to use a reference to it afterward — which would be a dangling reference, exactly the class of bug Rust's borrow checker exists to eliminate at compile time. The fix is almost always to return an owned value instead of a reference, or to restructure the code so the value's scope actually covers every use of the reference.
The exact error string
error[E0597]: `local_name` does not live long enough
--> src/main.rs:3:26
|
2 | let local_name = String::from("Ada");
| ---------- binding `local_name` declared here
3 | return &local_name;
| ^^^^^^^^^^ borrowed value does not live long enough
4 | }
| - `local_name` dropped here while still borrowed
Every E0597 message names the value, the line it was declared on, the line the borrow occurs, and — critically — the exact line where the value is dropped while a reference to it is still expected to be valid. That last line is the key: it tells you precisely where the value's scope ends relative to where the reference is still needed.
The scope-vs-lifetime mismatch, visually
The reference's required lifetime (bottom bar) extends past the value's actual scope (top bar). The borrow checker compares these two spans for every reference in the program — this is the entire mechanism, applied everywhere.
Cause 1: returning a reference to a local variable
fn make_name() -> &str {
let local_name = String::from("Ada");
&local_name // ❌ local_name is dropped when the function returns
}
// ✅ return the owned value itself — ownership transfers to the caller,
// so there's nothing to outlive:
fn make_name() -> String {
let local_name = String::from("Ada");
local_name
}
This is the unconditional, unfixable-by-annotation version of the error: a local variable's storage belongs to its enclosing function's stack frame, which is torn down the instant the function returns. There is no lifetime you can write that makes a reference to freed stack memory valid — the only real fix is to stop trying to hand out a reference at all and return the owned value, transferring ownership to the caller instead of pointing at something about to disappear.
Cause 2: a struct holding a reference that outlives its source
struct Wrapper<'a> { name: &'a str }
fn build() -> Wrapper<'static> {
let s = String::from("Ada");
Wrapper { name: &s } // ❌ `s` does not live long enough
} // `s` dropped here, but Wrapper claims to hold a 'static reference
// ✅ store an owned String instead of borrowing:
struct Wrapper { name: String }
fn build() -> Wrapper {
let s = String::from("Ada");
Wrapper { name: s }
}
A struct with a lifetime parameter is making a promise: every reference it holds must remain valid for at least as long as the struct itself is used. When the struct is built from a value local to the function that's supposed to hand the struct back out, that promise is impossible to keep — the reference inside would need to outlive the local variable it points to. Storing owned data (a String instead of a &str) sidesteps the whole problem by giving the struct its own copy of the data rather than a view into someone else's, at the cost of the extra allocation.
Cause 3: a temporary value dropped at the end of its statement
let first_word: &str = "hello world".split(' ').collect::<Vec<_>>()[0];
// ❌ the Vec from .collect() is a temporary — dropped at the end of this
// statement, taking its contents' backing storage with it
// ✅ bind the temporary to a name first, extending its scope:
let words: Vec<&str> = "hello world".split(' ').collect();
let first_word: &str = words[0];
An intermediate value produced mid-expression — the result of a chained method call that's never bound to a variable — is a temporary, and Rust drops temporaries at the end of the statement they're created in unless something extends their scope. Taking a reference into a temporary's contents and trying to use that reference past the end of the statement runs into exactly the same not-living-long-enough problem as any other value; giving the temporary an explicit name via let is what extends its lifetime to cover the rest of that variable's own scope.
Cause 4: a mutex/lock guard dropped before its reference is used
use std::sync::Mutex;
let data = Mutex::new(vec![1, 2, 3]);
let first = &data.lock().unwrap()[0]; // ❌ the MutexGuard temporary is
// dropped at the end of this line
println!("{}", first);
// ✅ bind the guard to a name so it stays alive for the block:
let guard = data.lock().unwrap();
let first = &guard[0];
println!("{}", first);
Calling .lock() returns a guard object (whose Drop implementation releases the lock), and if that guard is never bound to a name, it's a temporary that gets dropped at the end of its statement — releasing the lock and invalidating any reference taken into the locked data at the exact same moment. This is a specific, common instance of Cause 3 that trips up developers working with concurrency primitives specifically, since the error can look like a completely unrelated E0597 rather than an obviously-temporary-related mistake.
Common causes at a glance
| Situation | Root cause | Fix |
|---|---|---|
returning &local_var from a function | local variable dropped when the function returns | return an owned value instead |
| a struct with a lifetime holding local data | the struct's promised lifetime outlives its source | store an owned field instead of a reference |
| reference into a chained-call result | an unbound temporary is dropped at end of statement | bind the temporary to a let variable first |
reference into a Mutex/RwLock value | the guard temporary is dropped, releasing the lock | bind the guard to a name so it stays alive |
Why this happens: Rust checks scopes against reference needs at compile time
The borrow checker's job for this class of error boils down to one comparison, repeated everywhere a reference exists in a program: does the value being referenced remain alive for at least as long as the reference to it is used? A value's scope ends deterministically — at the closing brace of the block it was declared in, or at the end of a statement if it's an unbound temporary — and the compiler computes exactly how long each reference needs to remain valid based on how it's used afterward (returned, stored in a struct, held across a lock). E0597 fires the moment those two spans don't nest correctly: the reference's required span extends past the value's actual span. This is precisely the compile-time analysis that eliminates dangling-pointer bugs, one of the most common and dangerous classes of memory-safety defects in languages like C and C++, at zero runtime cost — the entire check happens during compilation and produces no code in the final binary.
Debugging checklist
- ✓ Find the "dropped here while still borrowed" line — that's exactly where the value's scope ends
- ✓ If you're returning a reference to something declared inside the function, return owned data instead
- ✓ If a struct holds a reference, consider whether it should own the data instead
- ✓ If the borrow is into a chained method-call result, bind the intermediate value to a
letfirst - ✓ Working with a
Mutex/RwLock? Bind the guard to a named variable before borrowing into it
Frequently Asked Questions
What does "X does not live long enough" mean in Rust?
You created a reference to a value, and Rust's borrow checker determined the value will be dropped (its memory freed) before the reference is done being used — which would leave the reference pointing at freed memory, a dangling reference. Rust refuses to compile this rather than allow the undefined behavior a dangling pointer would cause at runtime.
Why does returning a reference to a local variable always fail this way?
A variable declared inside a function is dropped the moment that function returns — its stack frame is gone. A reference to it necessarily points at memory that no longer belongs to anything the instant the function ends, so any attempt to return &local_variable fails E0597 unconditionally; there is no valid lifetime you could annotate your way out of, because the value itself doesn't survive past the function.
How is this different from a moved-value error (E0382)?
E0382 (borrow of moved value) is about ownership transfer — using a value after you've handed its ownership to something else. E0597 is about scope duration — a reference outliving the value it borrows from, regardless of ownership. Both are enforced by the same borrow checker, but they catch different classes of mistakes: one tracks who currently owns a value, the other tracks how long a borrowed reference is allowed to exist relative to its source.
Does this mean Rust prevents a whole category of C/C++ bugs?
Yes — a dangling pointer (using memory after it's been freed) is one of the most common sources of undefined behavior, memory corruption, and security vulnerabilities in C and C++. Rust's borrow checker performs exactly the scope-duration analysis needed to rule this out entirely at compile time, for zero runtime cost, which is one of the core selling points of the language's ownership model.
Can I fix this by adding an explicit lifetime annotation?
Only if the value genuinely does live long enough somewhere else and the annotation is just clarifying an ambiguous relationship the compiler couldn't infer on its own. A lifetime annotation describes an existing relationship between scopes; it cannot force a value to live longer than its actual scope allows. If the value is truly local and temporary, the fix is to return owned data or restructure the code, not to annotate around the problem.
Why does a temporary value in the middle of an expression sometimes cause this?
A temporary created mid-expression (e.g. the Vec returned by a chained method call) is dropped at the end of that statement unless it's bound to a variable — if you take a reference into it that outlives the statement, the borrow checker reports the temporary as not living long enough. Binding the temporary to a named let variable first extends its scope to that variable's own scope, which resolves it.
More Go, Rust & Java errors
Browse the full reference for Go, Rust, Java, and other backend language errors — exact message, cause, and fix.