How to fix Rust E0597 does not live long enough

Fix Rust E0597 by moving variable declarations to a larger scope or returning owned values instead of references to temporary data.

When references outlive their data

You create a string inside a loop, grab a reference to it, and push that reference into a vector outside the loop. The compiler rejects you with E0597: borrowed value does not live long enough. It feels like Rust is fighting you. It isn't. The compiler is pointing out that you're trying to hand someone a map to a house that you just demolished.

E0597 is the borrow checker's way of telling you that a reference is being used after the data it points to has been destroyed. In languages without ownership, this results in a dangling pointer. Dereferencing a dangling pointer reads garbage, corrupts memory, or crashes the program. Rust catches this at compile time. The error gives you the exact location of the borrow and the location where the value is dropped. Use that information to fix the scope or the ownership model.

The lease on a reference

A reference in Rust is a pointer with a strict lease. You can point at data, but only as long as that data exists. The data has an owner. The owner is responsible for cleaning up the data when it's no longer needed. When the owner drops the data, the memory is reclaimed. Any reference to that data becomes invalid.

Think of a reference like a key to a hotel room. The room is the data. The hotel management is the owner. When the guest checks out, the management changes the locks and reassigns the room. If you still hold a key, it no longer works. Using that key to enter the room might let you into someone else's space or a storage closet. Rust ensures you never hold a key after the room is reassigned. E0597 triggers when you try to use the key after checkout.

The compiler tracks scopes. A scope is a block of code delimited by braces. Variables declared in a scope are dropped when the scope ends. References must go out of scope before the data they point to is dropped. If a reference lives longer than the data, the compiler emits E0597.

Minimal example

This code triggers E0597 because the reference escapes the scope where the data lives.

fn main() {
    let reference;
    {
        // Create a String on the heap.
        let value = String::from("hello");
        // Try to borrow 'value' and store the reference outside the block.
        reference = &value;
        // E0597: 'value' is dropped at the end of this block,
        // but 'reference' is used later.
    }
    // 'value' is gone. 'reference' points to freed memory.
    println!("{reference}");
}

The error message points to the line reference = &value. It also shows a note indicating that value is dropped at the end of the inner block. The compiler knows that reference is used in println! after the block closes. The lifetime of value is too short for the lifetime of reference.

Walk through the failure

The inner block creates value. value owns a heap allocation containing the string data. You create reference, which points to value. The block ends. Rust runs the destructor for value. The heap memory is freed. reference still points to that address. If you print reference, you read freed memory. This is undefined behavior. Rust catches this at compile time. E0597 tells you the borrower lives longer than the lender.

The fix is to ensure the data lives as long as the reference. You can move the data declaration outside the block, or you can keep the reference usage inside the block. You can also return the owned data instead of a reference.

Realistic scenario: the collection trap

A common place to hit E0597 is when collecting references in a loop. You create temporary data in each iteration and try to store references to those temporaries.

fn main() {
    let mut results = Vec::new();
    for i in 0..5 {
        // Create a temporary String for this iteration.
        let temp = String::from(format!("Item {}", i));
        // Push a reference to 'temp' into the vector.
        results.push(&temp);
        // E0597: 'temp' is dropped at the end of the loop body,
        // but 'results' holds a reference to it.
    }
    // All references in 'results' are dangling.
    println!("{results:?}");
}

The loop creates temp in each iteration. temp is dropped at the end of the iteration. results holds a reference to temp. After the first iteration, results contains a reference to a dropped string. The compiler stops you immediately.

The fix is to store owned data in the vector. Move the string into the vector instead of borrowing it.

fn main() {
    let mut results = Vec::new();
    for i in 0..5 {
        // Create a String for this iteration.
        let temp = String::from(format!("Item {}", i));
        // Move ownership of 'temp' into the vector.
        results.push(temp);
        // 'temp' is moved. It lives as long as 'results'.
    }
    println!("{results:?}");
}

Now results owns the strings. The strings are dropped when results is dropped. No dangling references.

Drop order matters

Rust drops variables in reverse order of their declaration. This rule ensures that dependencies are cleaned up correctly. If variable B depends on variable A, B must be declared after A. B drops before A. If B holds a reference to A, B is dropped while A is still alive.

If you declare B before A, B drops after A. If B holds a reference to A, B tries to use A after A is gone. The compiler prevents this with E0597.

fn main() {
    let reference;
    let value = String::from("hello");
    reference = &value;
    // 'value' is declared after 'reference'.
    // 'value' is dropped before 'reference'.
    // 'reference' becomes dangling.
    // E0597.
}

The compiler sees that reference is declared first. reference drops last. value is declared second. value drops first. reference points to value. value is dropped while reference is still alive. Error.

Fix this by declaring the reference after the value.

fn main() {
    let value = String::from("hello");
    let reference = &value;
    // 'value' is declared first.
    // 'value' drops after 'reference'.
    // 'reference' is dropped while 'value' is alive.
    // Safe.
    println!("{reference}");
}

Now reference drops before value. The reference is valid for its entire lifetime.

How to fix E0597

E0597 is a logic error. The lifetimes are correct. The scopes are wrong. You need to adjust the code so the data lives as long as the reference. There are four main strategies.

Move the data declaration to an outer scope. This extends the lifetime of the data. Use this when the data is needed in multiple places and you can declare it earlier.

Return owned data instead of a reference. This transfers ownership to the caller. The caller decides when to drop the data. Use this when the function creates new data that needs to survive the function call.

Clone the data. This creates a copy with its own lifetime. Use this when you need independent data and the cost of cloning is acceptable. Cloning duplicates the heap allocation. It breaks the lifetime dependency.

Restructure the logic. Sometimes you don't need the reference to outlive the data. Move the usage inside the scope where the data lives. Use this when the reference is only needed temporarily. This avoids copying and keeps lifetimes tight.

Pitfalls and compiler errors

The error message for E0597 includes a note that explains the lifetime mismatch. Read the note. It tells you where the value is created and where it is dropped. It also tells you where the reference is used. Use this information to fix the scope.

E0597 is related to E0515. E0515 is returns a value referencing data owned by the current function. E0515 triggers when you try to return a reference to a local variable. E0597 triggers when you use a reference after the data is dropped, even if you don't return it. Both errors mean the same thing: the reference outlives the data.

A common pitfall is trying to fix E0597 by adding lifetime annotations. Lifetime annotations tell the compiler how lifetimes relate. They don't change the scopes. If the data is dropped too early, adding annotations won't help. The compiler will still emit E0597. Fix the scope first. Add annotations only if the compiler needs help inferring lifetimes.

Another pitfall is assuming that &String is the same as String. They are not. &String is a reference. It borrows the string. String is owned. It owns the data. If you need the data to outlive the scope, use String. If you only need to read the data temporarily, use &String.

Convention aside: Returning owned values is idiomatic. Rust's compiler is aggressive about optimizing allocations. If a function returns a String constructed from a local variable, the allocation often happens directly in the caller's stack frame. You don't need to return a reference to avoid an allocation. The compiler handles it. Focus on correctness first. If profiling shows an allocation bottleneck, optimize then.

Decision: when to use this vs alternatives

Use owned types when the data needs to outlive the scope where it was created. Return String instead of &str if the function creates new data. This transfers ownership to the caller. The caller controls the lifetime.

Use clone when you need a copy of the data that lives independently. This duplicates the heap allocation. Use this when the cost of cloning is acceptable and you need to break the lifetime dependency. Cloning is expensive for large data. Measure the impact.

Use scope adjustment when the reference is only needed temporarily. Move the usage inside the block where the data lives. This avoids copying and keeps lifetimes tight. This is the most efficient fix when the logic allows it.

Use Cow when you want to accept either owned or borrowed data and return a unified type. This allows callers to pass references or owned values without forcing a clone. Cow stands for "clone on write". It holds a reference until you need to modify the data. Then it clones. Use this for APIs that need flexibility.

Use Rc or Arc when multiple owners need to share the data. This adds reference counting overhead but allows the data to live as long as any owner holds a handle. Use Rc for single-threaded code. Use Arc for multi-threaded code. Reference counting is slower than ownership. Use it only when sharing is required.

Trust the borrow checker. It usually has a point. If you get E0597, the data is dying too soon. Move the data, return the value, or adjust the scope. Don't try to cheat the compiler. The compiler is protecting you from a crash.

Where to go next