Error E0716

"temporary value dropped while borrowed" — How to Fix

Fix Rust error E0716 by storing the temporary value in a variable or cloning it before borrowing.

The temporary vanishes before you can hold it

You're writing a parser. You extract a substring, trim whitespace, and pass a reference to a validation function. The code looks logical. The compiler rejects it with E0716: "temporary value dropped while borrowed". You're holding a reference to a value that ceases to exist the moment the line of code finishes.

Rust creates temporary values constantly. They bridge expressions and allocate memory on the fly. The compiler allows them, but it enforces a strict rule: a temporary lives only until the end of the statement. If you take a reference to a temporary and try to keep that reference alive past the statement boundary, the compiler stops you. The reference would point to freed memory. E0716 is the guard rail.

What a temporary actually is

A temporary is a value without a name. It has no variable binding. It exists solely to satisfy an expression. When you call String::from("hello") inside a larger expression, that String is a temporary. It lives on the heap. It has a drop implementation. But nothing owns it in the traditional sense. The compiler tracks it and schedules it for destruction at the end of the current statement.

Think of a temporary like a receipt printed at a checkout counter. The receipt exists while the machine prints it. If you try to staple the receipt to a file before the machine finishes, you're fighting the machine. Rust prevents you from stapling a receipt that gets shredded the instant the transaction ends. You need to keep the receipt yourself, or copy the info onto a permanent document.

Temporaries appear in several places:

  • Function calls that return owned values, like String::from or vec![1, 2, 3].
  • Method chains where the receiver is a temporary.
  • Literal values coerced to a type, like &"hello" becoming a String in some contexts.

The key property is the lifetime. A temporary's lifetime ends at the semicolon. If you assign a reference to a variable, that variable lives until the end of the scope. The reference outlives the value. Rust forbids this mismatch.

The minimal trap

The simplest way to trigger E0716 is to bind a reference to a temporary.

fn main() {
    // String::from allocates a String on the heap.
    // This String is a temporary with no owner.
    // Rust plans to drop it at the end of this statement.
    let s = &String::from("hello");
    // We store a reference in `s`.
    // `s` will live until the end of `main`.
    // The temporary String dies at the semicolon.
    // `s` now points to freed memory.
    // Rust rejects this with E0716.
}

The compiler output is direct. It tells you the temporary value is dropped while borrowed. The reference s requires the String to live longer than the statement allows.

Step-by-step: why the compiler panics

Rust analyzes lifetimes during compilation. It tracks where values are created and where they are dropped. Here is the sequence for the code above:

  1. String::from("hello") executes. A String is allocated on the heap.
  2. The compiler marks this String as a temporary. Its drop point is the end of the statement.
  3. & takes a reference to the temporary. The reference has a lifetime tied to the variable s.
  4. s is assigned the reference. s lives until the end of main.
  5. The statement ends. The compiler sees the temporary must be dropped.
  6. The compiler checks if any active borrows depend on the temporary. s is still alive and holds a borrow.
  7. Dropping the temporary would invalidate s. This is undefined behavior in systems languages. Rust rejects the code with E0716.

The error prevents a dangling reference. In C or C++, this code would compile. The String would be freed. s would point to garbage. Accessing s later would crash or read corrupted data. Rust eliminates this class of bug at compile time.

When temporaries are safe

E0716 only fires when the borrow outlives the temporary. If the borrow ends before the temporary is dropped, the code compiles. This happens frequently with function arguments.

fn main() {
    // String::from creates a temporary.
    // calculate_length takes a reference.
    // The borrow lasts only for the duration of the call.
    // calculate_length returns before the statement ends.
    // The temporary lives until the semicolon.
    // The borrow is shorter than the temporary.
    // This is safe. No error.
    let len = calculate_length(&String::from("hello"));
    println!("Length: {}", len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

The temporary lives until the semicolon. The borrow exists only while calculate_length runs. The function returns a usize, which does not borrow the string. The borrow is over. The temporary can be dropped safely. The compiler allows this pattern. It extends the temporary's lifetime just enough to cover the expression, but never beyond the statement.

Real-world scenario: config parsing

In real code, E0716 often appears when parsing data. You read a config, extract a field, and return a reference to that field. The config data is a temporary, and the reference tries to escape.

fn main() {
    // parse_config returns a String.
    // This String is a temporary.
    // get_name takes a reference and returns a &str.
    // The &str borrows from the config String.
    // `name` stores the &str.
    // `name` lives until the end of main.
    // The config String drops at the semicolon.
    // `name` borrows a dropped value.
    // E0716 triggers.
    let name = get_name(&parse_config());
    println!("User: {}", name);
}

fn parse_config() -> String {
    String::from("user: admin")
}

fn get_name(config: &String) -> &str {
    // Returns a slice of the config.
    // This slice borrows from `config`.
    config.split(':').next().unwrap()
}

The fix is to bind the temporary. Give the value a name so it lives long enough.

fn main() {
    // Store the temporary in a variable.
    // Now the String has an owner.
    // It lives until the end of `main`.
    let config = parse_config();
    // Borrow from the owned variable.
    // The borrow lives as long as `config` does.
    let name = get_name(&config);
    println!("User: {}", name);
}

fn parse_config() -> String {
    String::from("user: admin")
}

fn get_name(config: &String) -> &str {
    config.split(':').next().unwrap()
}

The code compiles. config owns the String. name borrows from config. Both live until the end of main. The borrow checker is satisfied.

Bind the temporary. A named variable is the cheapest way to keep data alive.

Pitfalls and edge cases

Method chains on temporaries

Method chains can hide temporaries. If the chain returns a reference, you risk E0716.

fn main() {
    // vec! creates a temporary Vec.
    // first returns Option<&i32>.
    // The Option borrows the Vec.
    // `first_elem` stores the Option.
    // The Vec drops at the semicolon.
    // `first_elem` holds a dangling reference.
    // E0716 triggers.
    let first_elem = vec![1, 2, 3].first();
}

The Vec is temporary. first returns an Option that borrows the Vec. Storing the Option extends the borrow past the statement. The fix is the same: bind the collection.

fn main() {
    let items = vec![1, 2, 3];
    let first_elem = items.first();
}

NLL does not save you here

Non-Lexical Lifetimes (NLL) improved borrow checking by tracking exact usage points. NLL allows borrows to end earlier than the scope. NLL does not extend temporaries beyond the statement. A temporary still drops at the semicolon. NLL helps with let x = &y; use(x); where y is dropped later, but let x = &temporary; still fails. The temporary has no owner to extend its life.

Cloning is not always the fix

Cloning creates an owned copy. It solves E0716 by removing the borrow. But cloning allocates memory. It changes semantics. Use cloning only when you need an independent copy. Binding the temporary is usually better. It avoids allocation and preserves the original data.

Convention aside: The community calls this the "bind early" pattern. If the compiler complains about a temporary, introduce a let binding. It makes the code readable and satisfies the compiler. Reserve cloning for cases where you need mutation or ownership transfer.

Decision: how to fix E0716

Use a let binding when you need to extend the lifetime of a value so other variables can borrow from it. Bind the temporary to a name, and the value lives until the end of the scope. This is the standard fix for E0716.

Use .clone() when you need an independent copy of the data and don't care about the extra allocation. This is common when passing data to a function that takes ownership, or when you need to mutate the copy without affecting the original.

Use .to_owned() when you have a reference or a temporary and need an owned String or Vec. This converts the borrowed data into a heap-allocated value you control. It is semantically clearer than .clone() for types implementing ToOwned.

Reach for Cow when you want to avoid cloning if the data is already owned, but handle borrowed data gracefully. This is an optimization for functions that might receive either owned or borrowed input. Cow wraps the data and clones only when mutation is required.

Trust the borrow checker. It usually has a point.

Where to go next