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.
The error
Section titled “The error”Chaining a borrow off a freshly created value looks natural:
fn main() { let name = "Alice"; let greeting = format!("Hello, {name}!").as_str(); println!("{greeting}");}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 use4 | 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 fix: bind it to a name first
Section titled “The fix: bind it to a name first”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:
fn main() { let name = "Alice"; let text = format!("Hello, {name}!"); let greeting = text.as_str(); println!("{greeting}");}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 same error from a collection
Section titled “The same error from a collection”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:
fn main() { let first = vec![10, 20, 30].first().unwrap(); println!("first is {first}");}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 use3 | 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:
fn main() { let numbers = vec![10, 20, 30]; let first = numbers.first().unwrap(); println!("first is {first}");}This prints:
first is 10Why 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.
fn main() { let r = &String::from("hello"); println!("r is {r}");}This prints:
r is hellofn main() { let r = &vec![1, 2, 3]; println!("{:?}", r);}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.
Not this error
Section titled “Not this error”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.