error[E0597]: `x` does not live long enough

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

fn make_ref() -> &str { local_name's scope (owns the data) declared ← dropped here &local_name needs to stay valid this whole time (returned to the caller) start value dropped reference still needed the gap between "dropped" and "still needed" is exactly what E0597 rejects

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

SituationRoot causeFix
returning &local_var from a functionlocal variable dropped when the function returnsreturn an owned value instead
a struct with a lifetime holding local datathe struct's promised lifetime outlives its sourcestore an owned field instead of a reference
reference into a chained-call resultan unbound temporary is dropped at end of statementbind the temporary to a let variable first
reference into a Mutex/RwLock valuethe guard temporary is dropped, releasing the lockbind 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

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.

All Error References Rust: E0382 borrow of moved value HTTP Status Codes
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.