Skip to content
rusthacks.dev
Rust

Rust Error E0597 Borrowed Value Does Not Live Long Enough

E0597 means a reference outlives the value it points to. See the two common shapes, a block that ends too early and a struct that outlives its data, and how to fix each.

7 min read
Summarize in AI
Flat illustration for the post “Rust Error E0597 Borrowed Value Does Not Live Long Enough”, composed around borrow.

error[E0597]: ... does not live long enough says you have a reference to a value that is destroyed before the reference's last use. In C this is a dangling pointer, and the program reads freed or reused stack memory with no complaint. In Rust the compiler rejects it and prints the three lines you need: where the value was declared, where it was dropped, and where the reference is used afterward. This post reproduces the three situations that produce this error most often, shows the compiler's real output on Rust 1.99, and shows the fix for each, which is nearly always the same idea: make the value live as long as the reference, or make the reference own its data.

What the error means

Every value lives from the line that creates it to the end of the block that owns it, where it is dropped. A reference is only valid while the value it points to is alive. The compiler checks that every reference's lifetime fits inside the lifetime of its target. When it does not, you get E0597, and the three annotations in the output describe the violation exactly: the binding, the point where it dies, and the use after death.

Form one: a reference that escapes its block

The textbook version has an inner scope:

src/main.rsrust
fn main() {
    let r;
    {
        let x = 5;
        r = &x;
    }
    println!("r: {r}");
}
Text
error[E0597]: `x` does not live long enough
 --> e0597a.rs:5:13
  |
4 |         let x = 5;
  |             - binding `x` declared here
5 |         r = &x;
  |             ^^ borrowed value does not live long enough
6 |     }
  |     - `x` dropped here while still borrowed
7 |     println!("r: {r}");
  |                   - borrow later used here

Read the three annotated lines as a timeline. x is created on line 4. On line 6 the closing brace drops it. On line 7 r, which still points at x, is used. The program would read a dead variable. The fix is to widen the lifetime of the value so it outlives the use, by declaring it in the same scope:

src/main.rsrust
fn main() {
    let x = 5;
    let r = &x;
    println!("r: {r}");
}
Text
r: 5

Real programs rarely have a bare inner block like this, but they have equivalents everywhere: a variable created inside an if branch, a match arm, a loop body, or a helper function whose result is stored somewhere longer-lived. The pattern to look for is a reference stored in a variable declared outside the block and a value created inside it.

Form two: a struct that borrows something shorter-lived

The second form is a struct with a lifetime parameter, such as a parser that holds a slice of its input:

src/main.rsrust
struct Parser<'a> {
    input: &'a str,
}

fn main() {
    let parser;
    {
        let text = String::from("let x = 1;");
        parser = Parser { input: &text };
    }
    println!("{}", parser.input);
}
Text
error[E0597]: `text` does not live long enough
  --> e0597b.rs:9:34
   |
 8 |         let text = String::from("let x = 1;");
   |             ---- binding `text` declared here
 9 |         parser = Parser { input: &text };
   |                                  ^^^^^ borrowed value does not live long enough
10 |     }
   |     - `text` dropped here while still borrowed
11 |     println!("{}", parser.input);
   |                    ------------ borrow later used here

The 'a in Parser<'a> is a promise that the struct does not outlive the text it points into. Here the parser outlives the String it borrows, which is precisely the promise broken. The fix is the same as before: declare the owner first, so it lives at least as long as the borrower. Rust drops locals in reverse order of declaration, so the borrowed value must come first:

src/main.rsrust
struct Parser<'a> {
    input: &'a str,
}

fn main() {
    let text = String::from("let x = 1;"); // declared first, so it outlives the parser
    let parser = Parser { input: &text };
    println!("{}", parser.input);
}
Text
let x = 1;

When the parser has to be returned from a function that created the text, you cannot borrow, because the text dies at the end of the function. Make the struct own its data (input: String) and pay the allocation once, or take the text as a parameter so the caller owns it. The choice is a design decision about who owns the input, and the lifetime error is the compiler asking you to make it.

Form three: a collection of references to loop-local values

The version that catches people in real code stores references in a map or vector that outlives the loop that fills it:

src/main.rsrust
use std::collections::HashMap;

fn main() {
    let mut index: HashMap<&str, usize> = HashMap::new();
    for i in 0..3 {
        let key = format!("item-{i}");
        index.insert(&key, i);
    }
    println!("{}", index.len());
}
Text
error[E0597]: `key` does not live long enough
 --> e0597c.rs:7:22
  |
6 |         let key = format!("item-{i}");
  |             --- binding `key` declared here
7 |         index.insert(&key, i);
  |                      ^^^^ borrowed value does not live long enough
8 |     }
  |     - `key` dropped here while still borrowed
9 |     println!("{}", index.len());
  |                    ----- borrow later used here

Each iteration creates a new String called key and drops it at the end of the iteration. The map holds &str keys that point into those dropped strings. After the loop, every key in the map would be a dangling reference. The honest fix is to let the map own its keys:

src/main.rsrust
use std::collections::HashMap;

fn main() {
    let mut index: HashMap<String, usize> = HashMap::new();
    for i in 0..3 {
        let key = format!("item-{i}");
        index.insert(key, i); // the map owns the key now
    }
    println!("{}", index.len());
}
Text
3

The change from HashMap<&str, usize> to HashMap<String, usize> is where the data lives. A map of borrowed keys is fine when the keys come from a source that lives longer, such as slices of a big input text read once at the start. When the keys are created along the way, the map must own them.

A fourth form: returning a reference to a local

A function cannot return a reference to something it created. The compiler reports that one as E0515 ("cannot return value referencing local variable") rather than E0597, but it is the same idea. This function uppercases its input and returns the longest line, as a slice into the uppercased copy:

src/main.rsrust
fn longest_line(text: &str) -> &str {
    let upper = text.to_uppercase();
    upper.lines().max_by_key(|l| l.len()).unwrap_or("")
}
Text
error[E0515]: cannot return value referencing local variable `upper`
 --> e0515.rs:3:5
  |
3 |     upper.lines().max_by_key(|l| l.len()).unwrap_or("")
  |     -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |     |
  |     returns a value referencing data owned by the current function
  |     `upper` is borrowed here

The signature promises a &str borrowed from the input text, but the slice points into upper, a new String that is freed when the function returns. The honest signature returns an owned String, because the result is new data and not a view of the input:

src/main.rsrust
fn longest_line(text: &str) -> String {
    text.to_uppercase().lines().max_by_key(|l| l.len()).unwrap_or("").to_string()
}
Text
A LONGER LINE

The general rule for return types: if the result is a piece of an input, return a reference and tie it to that input; if the function creates the data, return an owned value. A reference return type is a statement about where the data lives, and a function that builds data cannot make that statement.

Choosing the fix

SymptomFix
Value declared in an inner block, reference used outsideMove the declaration out so the owner is declared first
Struct with a lifetime, outlived by the data it borrowsDeclare the data before the struct, or make the struct own it
Collection of references filled with loop-local valuesStore owned values, String instead of &str
Function returning a reference to its own localReturn the owned value, or accept the owner as a parameter

Do not reach for 'static or Box::leak to silence the error. They make the compiler quiet by keeping the data alive forever, which turns a compile-time error into a memory leak. And do not add lifetime annotations hoping the compiler will relent; a lifetime annotation describes a relationship, it never extends how long a value lives. The value lives exactly as long as its owner does, and the error is telling you the owner is too short.

For the surrounding concepts, see ownership in Rust explained, and for the other two errors in this family, E0382, use of a moved value and E0502, mutable and immutable borrows.

Taggede0597
Share

Keep reading

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.