How to fix Rust E0716 temporary value dropped while borrowed

Fix Rust E0716 by storing temporary values in variables to extend their lifetime before borrowing them.

The reference to a value that vanishes instantly

You write let name = &String::from("Alice");. The compiler rejects you with E0716: temporary value dropped while borrowed. You are trying to keep a reference to data that Rust deletes the moment the line finishes executing. The value exists for a fraction of a nanosecond, you point at it, and then it is gone. Your reference becomes a dangling pointer. Rust catches this at compile time.

This error happens whenever you borrow the result of a function or expression that returns an owned value, without first storing that value in a variable. The fix is almost always the same: give the value a name. Bind it to a variable. The variable extends the lifetime of the data so your borrow remains valid.

Temporaries and the statement boundary

Rust creates values on the fly all the time. When you call String::from("hello"), a new String is allocated on the heap. When you call vec![1, 2, 3], a new Vec is built. When you call format!("User {}", id), a new string is computed. These are owned values. They hold memory. They must be cleaned up.

If you do not assign an owned value to a variable, Rust treats it as a temporary. Temporaries are optimized away aggressively. The compiler drops them at the end of the enclosing statement. A statement ends at the semicolon. If you borrow a temporary, the borrow tries to outlive the value. The temporary dies at the semicolon. Your reference points to freed memory. The compiler refuses to allow this.

Think of a temporary like a note written on a napkin that self-destructs the moment you stop looking at it. You can glance at the napkin. You cannot hand the napkin to a friend. You cannot read the napkin next week. If you need the data to persist, you must copy it onto a permanent sheet. In Rust, binding to a variable is the permanent sheet.

/// Demonstrates E0716 by borrowing a temporary String.
fn main() {
    // String::from creates an owned String on the heap.
    // Because we do not bind it to a variable, it is a temporary.
    // The compiler drops this temporary at the end of the statement.
    // The borrow tries to keep a reference past the drop point.
    let r = &String::from("hello"); // E0716: temporary value dropped while borrowed
}

The error message tells you exactly what happened. A temporary value was created. You borrowed it. The temporary was dropped. The borrow is invalid. The compiler enforces this rule to prevent use-after-free bugs. You cannot opt out. You must change the code so the data lives long enough.

Binding extends the lifetime

The solution is to bind the temporary to a variable. When you write let x = value;, the temporary is no longer a temporary. It becomes the initializer for x. The value lives until the end of x's scope. You can now borrow x safely. The borrow checker sees that x owns the data and that x outlives the borrow.

/// Fixes E0716 by binding the temporary to a named variable.
fn main() {
    // Binding to `s` extends the lifetime of the String.
    // The value now lives until the end of `s`'s scope.
    let s = String::from("hello");
    
    // The borrow is valid because `s` owns the data.
    // `s` will not be dropped until the end of main.
    let r = &s;
    
    println!("{}", r);
}

This is the core mechanic. Binding changes the lifetime rules. Temporaries die at the statement boundary. Variables die at the scope boundary. By binding, you move the value from the temporary zone to the variable zone. The data survives. Your reference survives.

Convention aside: when you see String::from("literal"), the community often prefers let s = "literal".to_string(); if the string is computed, but String::from is perfectly idiomatic for literals. Both create an owned String. The choice is stylistic. The lifetime behavior is identical.

Realistic scenarios where E0716 strikes

E0716 appears in code that looks reasonable at first glance. You are chaining methods. You are building a struct. You are returning a reference. The pattern is the same: an owned value is created inline, and you try to borrow it.

Consider a function that formats a message. You want to return a reference to the message to avoid allocation. You write &format!("Error: {}", code). The format! macro returns a String. That String is a temporary. You borrow it. The temporary drops. E0716 fires.

/// A realistic attempt to return a reference to a formatted string.
/// This fails because format! returns an owned String.
fn get_error_message(code: u32) -> &str {
    // format! returns a String.
    // The String is a temporary value.
    // Borrowing it creates a reference to data that vanishes.
    &format!("Error code: {}", code) // E0716
}

You cannot return a reference to a temporary. The fix depends on what you actually need. If the caller needs the string, return the owned String. If you want to avoid allocation, you cannot use format!. You must use a static string or a buffer owned by the caller.

/// Returns an owned String. The caller takes ownership.
/// No borrow is involved. No temporary issue.
fn get_error_message(code: u32) -> String {
    format!("Error code: {}", code)
}

Another common case is struct initialization. You define a struct with a reference field. You try to initialize it with a reference to a temporary.

struct Config {
    name: &'static str,
}

/// Attempting to store a reference to a temporary in a struct.
fn load_config() -> Config {
    // String::from creates a temporary.
    // The reference would dangle immediately.
    Config {
        name: &String::from("Production"), // E0716
    }
}

The struct expects a &'static str. A temporary String is not static. It lives on the heap and gets dropped. You cannot put a reference to it in the struct. The fix is to use a string literal, which is static, or change the struct to own the string.

struct Config {
    // Change to owned String if the value is dynamic.
    name: String,
}

fn load_config() -> Config {
    // Now the struct owns the data.
    // No borrow. No temporary.
    Config {
        name: String::from("Production"),
    }
}

Don't fight the ownership model here. If the data is created at runtime, the struct should own it. References are for data that lives elsewhere.

Method chains and hidden temporaries

Method chains hide temporaries behind syntax. You write let x = &vec![1, 2, 3].iter().map(|n| n * 2).collect::<Vec<_>>();. The collect call returns a Vec. That Vec is the result of the chain. It is a temporary. You borrow it. E0716.

The chain creates a value. The borrow tries to capture it. The value drops. The borrow fails. The fix is to break the chain. Bind the result.

/// Breaking a chain to bind the temporary.
fn process_data() {
    // collect returns a Vec.
    // Binding to `result` extends the lifetime.
    let result = vec![1, 2, 3]
        .iter()
        .map(|n| n * 2)
        .collect::<Vec<_>>();
    
    // Now the borrow is valid.
    let first = &result[0];
    println!("{}", first);
}

This pattern appears often with iterators, parsers, and builders. Any expression that returns an owned type creates a temporary if not bound. to_string(), clone(), unwrap(), expect(), build(). If you borrow the result of these without binding, you risk E0716.

Convention aside: the community calls this the "bind early" habit. When a chain produces an owned value, bind it immediately. Do not try to borrow the chain result inline. It makes the code clearer and avoids lifetime errors.

Pitfalls and related errors

E0716 has cousins. E0515 is "cannot return reference to local variable". This happens when you try to return a reference to a value created inside a function. The value is local. It drops when the function returns. The reference would dangle. E0716 is the same problem, but it happens in a let binding or expression, not a return.

E0597 is "borrowed value does not live long enough". This is a broader error. It covers cases where a borrow outlives the owner, even if the owner is a variable. E0716 is specific to temporaries.

A subtle pitfall is confusing &str literals with String. "hello" is a &str. It is a reference to data stored in the binary. It has a 'static lifetime. It never drops. You can borrow it freely. String::from("hello") creates a String. It is owned. It drops. You cannot borrow it without binding.

/// String literals are static references.
/// They do not create temporaries.
fn main() {
    // "hello" is a &'static str.
    // It lives for the entire program.
    // No temporary. No E0716.
    let r = "hello";
    
    println!("{}", r);
}

If you need a string that lives forever, use a literal. If you need a string that is computed at runtime, you must own it or borrow from something that owns it.

Another pitfall is &vec![...]. The vec! macro returns a Vec. It is a temporary. Borrowing it fails. Use &[1, 2, 3] instead. Array literals create arrays. &[1, 2, 3] creates a slice reference to a static array. This works because the array is static.

/// Using a slice literal instead of a vec temporary.
fn main() {
    // vec! returns a Vec. Temporary. Borrow fails.
    // let bad = &vec![1, 2, 3]; // E0716
    
    // [1, 2, 3] is an array.
    // &[1, 2, 3] is a reference to a static array.
    // This is valid.
    let slice = &[1, 2, 3];
    
    println!("{:?}", slice);
}

Know the difference between owned collections and static slices. Use slices for read-only data known at compile time. Use Vec for dynamic data.

Decision matrix

Use let binding when you need to borrow the result of a function that returns an owned value. Bind the result to a variable first. Borrow the variable. The variable extends the lifetime.

Use &str literals when the data is known at compile time and does not change. Literals are static. They never drop. You can borrow them without binding.

Use owned return values when a function computes new data. Return String, Vec, or other owned types. The caller takes ownership. No borrow issues.

Use static slices when you need a read-only collection of known values. &[1, 2, 3] works. &vec![1, 2, 3] fails. Prefer slices for constants.

Use String fields in structs when the data is created at runtime. References in structs require the data to live elsewhere. Owned fields are simpler for dynamic data.

Trust the temporary rules. They protect you from dangling pointers. Bind the value. Borrow the binding.

Where to go next