Skip to content
Select theme

How to fix error E0716: temporary value dropped while borrowed

Short answer: a reference is pointing at a value with no name — a temporary — and the temporary is dropped at the end of the very statement that created it. The fix is the one the error note suggests: bind the value to a let first, then borrow from the binding, so the value lives in a scope instead of dying at the semicolon.

Chaining a borrow off a freshly created value looks natural:

Compiling that with cargo build prints:

error[E0716]: temporary value dropped while borrowed
--> src/main.rs:3:20
|
3 | let greeting = format!("Hello, {name}!").as_str();
| ^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
4 | println!("{greeting}");
| -------- borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
For more information about this error, try `rustc --explain E0716`.

The value in question is the String that format! returns. It is a temporary: an expression result that was never assigned to a variable. A temporary lives exactly as long as the statement it was created in — the compiler drops it at the semicolon. as_str() returns a &str pointing inside that String, and the new binding greeting holds the borrow, but the borrowed thing itself has no owner and no scope to live in. When the statement ends, the String is freed, and greeting would be left pointing at freed memory — so the compiler refuses.

This is the same shape of conflict as E0597, with one difference: there the value had a name and a scope, and the fix was to widen the scope. Here the value never had a name at all, so there is no scope to widen — one has to be created.

The note: says it directly: consider using a let binding to create a longer lived value. Split the chain and give the String a binding:

This prints:

Hello, Alice!

Nothing about the borrow changed — text.as_str() is the same call, producing the same &str. What changed is that text is a named binding now, so the String it owns lives until the end of the block instead of the end of the statement, and greeting’s borrow fits comfortably inside that lifetime. In most real code you can stop one step earlier and just keep using text directly — the binding exists so the value has somewhere to live, whether or not you need the &str separately.

The second common shape hides the temporary behind a method chain. first() returns a reference into the vector it is called on — and the vector here is a temporary:

error[E0716]: temporary value dropped while borrowed
--> src/main.rs:2:17
|
2 | let first = vec![10, 20, 30].first().unwrap();
| ^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
3 | println!("first is {first}");
| ----- borrow later used here
|
help: consider using a `let` binding to create a longer lived value
|
2 ~ let binding = vec![10, 20, 30];
3 ~ let first = binding.first().unwrap();
|
For more information about this error, try `rustc --explain E0716`.

Same three markers, same story: the Vec built by vec![] is a temporary, first() borrows into it, and the borrow would outlive the statement that created the vector. This time the compiler also prints a help: block spelling out the fix — bind the temporary first. Applying exactly that:

This prints:

first is 10

Why let r = &String::from("hello"); compiles anyway

Section titled “Why let r = &String::from("hello"); compiles anyway”

The one that confuses people: borrowing a temporary directly in a let initializer produces no error at all.

This prints:

r is hello

This prints:

[1, 2, 3]

Both compile because of temporary lifetime extension: in a let initializer of the form let r = &expr;, the compiler keeps the temporary alive for the whole enclosing block instead of dropping it at the semicolon. The reference is still never allowed to outlive the value — the value’s drop point has simply been moved to match the reference.

The catch is that extension only applies when the let initializer borrows the temporary directly. Go through a method call on the way — &temp.as_str(), or .as_str() without the leading & as in the first example — and the extension no longer fires, because the reference now comes from the method’s return value, not from the & in the initializer. That is exactly the gap between let r = &String::from("hello");, which compiles, and let greeting = format!("Hello, {name}!").as_str();, which does not. The safe habit is to ignore the special case entirely and bind the value to a name first; it always works, and it reads the same to a human.

When the borrowed value has a binding and dies at the end of its own scope — a variable declared in an inner block, a loop local — the code is E0597 (borrowed value does not live long enough). There the fix is to move the value or the use so the scope covers the borrow. E0716 is the no-binding version of the same conflict: the value was never more than an expression result, so the only fix is to give it one.