Skip to content

Rust Error E0716: Temporary Value Dropped While Borrowed

E0716 means: you borrowed from a value that has no variable name, and then kept using that borrow on a later line. Rust throws such nameless values away at the end of the statement, so the borrow would point at nothing. It looks scary, but it’s one of the easiest borrow errors to fix: usually you just split one line into two. (This page is part of Rust compiler errors explained.)

error[E0716]: temporary value dropped while borrowed
--> src/main.rs:2:13
|
2 | let s = String::from("hi").as_str(); // s borrows a String that has no name
| ^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
3 | println!("{s}"); // ❌ that String is already gone
| - borrow later used here
|
help: consider using a `let` binding to create a longer lived value
|
2 ~ let binding = String::from("hi");
3 ~ let s = binding.as_str(); // s borrows a String that has no name
|

This is the program that produces it:

fn main() {
let s = String::from("hi").as_str(); // s borrows a String that has no name
println!("{s}"); // ❌ that String is already gone
}

A temporary is a value that is created in the middle of an expression but never stored in a variable. In the line above, String::from("hi") builds a real String, but nothing owns it: there is no let name = ... for it. Rust drops (cleans up) a temporary at the end of the statement, which is the ;.

Read the three labels from left to right:

  1. ^^^^ under String::from("hi"): here a temporary String is created.
  2. - at the end of the line: here, at the ;, that String is freed.
  3. - under s on line 3: but s (a &str pointing into that String) is still used here.

Think of a waiter who writes your order on a sticky note and throws the note away as soon as they walk off. If you tell a friend “the order is on that note”, your friend finds nothing. You have to keep the note yourself, or write the order down somewhere that stays.

The help: part of the message is usually the answer: create a variable (rustc calls it binding) so the value has an owner that lives longer.

This is the same rule as E0597: a reference must never outlive the value it points to, or it becomes a dangling reference to freed memory. The only difference is that in E0597 the value has a name and dies at a }, while in E0716 the value has no name and dies at the ;.

Using a temporary inside the same statement is completely fine. println!("{}", String::from("hi").as_str()); compiles, because the borrow ends before the ;. The error only appears when the borrow is still needed on a later line. See When does a borrow end? and Lifetimes.

There is one helpful exception, called temporary lifetime extension. If you put & (or &mut) directly in front of the temporary in a let, Rust quietly keeps the temporary alive as long as the variable. That’s why these lines compile:

struct Label<'a> {
text: &'a str,
}
fn make_title() -> String {
String::from("Inbox")
}
fn main() {
let greeting = &String::from("hi"); // ✅ extended: & right in the let
let queue = &mut vec![1, 2, 3]; // ✅ extended
queue.push(4);
let label = Label { text: &make_title() }; // ✅ extended: & directly in a struct literal
println!("{greeting} {queue:?} {}", label.text);
}

Output:

hi [1, 2, 3, 4] Inbox
▶ Try it Yourself

The extension does not pass through a method call or a function call. String::from("hi").as_str(), make_title().as_str(), vec![1, 2].iter() and shout(&make_title()) all borrow the temporary inside a call, so the temporary still dies at the ; and you get E0716. Don’t try to memorize every case: if you see E0716, give the value a name.

Choose this almost every time. It’s what the compiler suggests, and it’s the right answer for most E0716 errors. Store the value in its own variable first, then borrow from that variable on the next line.

A very common case is splitting a line of input. read_command() returns a new String, .trim() and .split(' ') borrow from it, and the Vec<&str> keeps those borrows:

let parts: Vec<&str> = read_command().trim().split(' ').collect(); // ❌ E0716

Rust points at read_command() and says it is freed at the end of the statement. Give it a name:

fn read_command() -> String {
// In a real program this would read a line from stdin.
String::from(" add milk eggs \n")
}
fn main() {
let line = read_command(); // the String now lives until the end of main
let parts: Vec<&str> = line.trim().split(' ').collect();
println!("{parts:?}");
println!("command: {}, items: {}", parts[0], parts.len() - 1);
}

Output:

["add", "milk", "eggs"]
command: add, items: 2
▶ Try it Yourself

You’ll hit exactly this with real input too: lines.next().unwrap().unwrap().trim().split(' ').collect() on io::stdin().lines() gives E0716 for the same reason. Put let line = lines.next().unwrap().unwrap(); on its own line first. See Strings.

Fix 2: Keep owned values instead of references

Section titled “Fix 2: Keep owned values instead of references”

Choose this when the borrow needs to live longer than any variable you could create, for example in a struct you pass around, or in a Vec you fill inside a loop. A let inside a loop would be dropped at the end of each pass, which only turns E0716 into E0597.

Here are two broken versions. A struct holding a &str from a temporary, and a loop that pushes borrows of new strings:

let label = Label { text: make_title().as_str() }; // ❌ E0716
let mut names: Vec<&str> = Vec::new();
for name in ["ferris", "corro"] {
names.push(name.to_uppercase().as_str()); // ❌ E0716
}

The fix is to let the struct and the Vec own their strings. Use String instead of &str, and drop the .as_str():

struct Label {
text: String, // owned, so no lifetime needed
}
fn make_title() -> String {
String::from("Inbox")
}
fn main() {
let label = Label { text: make_title() }; // the String moves into the Label
println!("Label: {}", label.text);
let mut names: Vec<String> = Vec::new();
for name in ["ferris", "corro"] {
names.push(name.to_uppercase()); // the Vec owns each new String
}
println!("{names:?}");
}

Output:

Label: Inbox
["FERRIS", "CORRO"]
▶ Try it Yourself

For beginners, owned fields (String, Vec<T>) are almost always the easiest choice. A struct with a &str field needs a lifetime and can never outlive the data it borrows. See Structs that hold references and Structs.

Choose this when the temporary is the start of an iterator chain. These lines create a Vec with vec![...], borrow it with .iter(), and keep the iterator (or its result) for later:

let doubled = &mut vec![1, 2, 3].iter().map(|n| n * 10); // ❌ E0716
let best = vec![72, 95, 88].iter().max(); // ❌ E0716

The &mut in front doesn’t help: lifetime extension keeps the iterator alive, but not the Vec that .iter() borrows from. Two ways out:

  • Use into_iter() so the iterator owns the Vec (rustc suggests this too). Then you don’t need &mut either, just let mut.
  • Finish the chain with an owned result, like .copied(), .cloned(), .sum() or .collect::<Vec<_>>() of plain numbers, so nothing borrows the temporary after the ;.
fn main() {
// The iterator owns the Vec, so nothing borrows a temporary
let mut doubled = vec![1, 2, 3].into_iter().map(|n| n * 10);
println!("{:?}", doubled.next());
println!("{:?}", doubled.next());
// .copied() turns Option<&i32> into Option<i32>, a value with no borrow
let best = vec![72, 95, 88].iter().max().copied();
println!("Best: {best:?}");
}

Output:

Some(10)
Some(20)
Best: Some(95)
▶ Try it Yourself

If you need the Vec again later, go back to Fix 1: let scores = vec![72, 95, 88]; and then scores.iter().max(). See Iterators.

Locking a Mutex in the middle of a chain. let first = names.lock().unwrap().first(); gives E0716, because the lock guard is a temporary. When it’s dropped, the lock is released, so first can’t keep pointing inside. Fix: let guard = names.lock().unwrap(); then guard.first(), or copy the data out in one go with .first().cloned(). See Shared State.

Passing &temporary to a function that returns a reference. With fn shout(s: &str) -> &str, the line let t = shout(&make_title()); gives E0716: the returned &str borrows the temporary String, and lifetime extension doesn’t reach through a function call. Fix: let title = make_title(); then shout(&title).

Returning it from a function. If you write make_title().as_str() as the last line of a function that returns a reference (like -> &'static str), you get E0515 (“cannot return value referencing temporary value”) instead. Fix: return the String.

Quick check

1. In Rust, what is a "temporary"?

2. let s = String::from("hi").as_str(); followed by println!("{s}"); gives E0716. What is the usual fix?

3. Which line compiles, thanks to temporary lifetime extension?

Score: 0 / 3