🎓 A1Learner
🔍

Rust Temporary Lifetimes

A temporary is a value made in the middle of an expression, with no name of its own. Temporary lifetime rules decide when Rust drops it.

A temporary normally dies at the semicolon

This String is built, measured, and dropped inside a single statement. Nothing may borrow it afterwards:

println!("len = {}", String::from("hello").len());
// the temporary String is dropped here, at the ;

A borrow in a let stretches the temporary

Bind a reference to a temporary in a let. Rust then extends the temporary to the end of the block. So the reference stays valid:

let greeting = &String::from("hello");
println!("{}", greeting);

Containers carry the stretch along

The stretch reaches through simple wrappers. A temporary inside Some(...) is extended too, because the borrow wraps the whole container:

let boxed = Some(&String::from("inside"));
println!("{:?}", boxed);

A function call in between breaks it

Here identity just hands its argument back. But the call sits between the temporary and the borrow. So no extension happens. The temporary dies at the semicolon:

let r = identity(&String::from("hi")); // error E0716: temporary dropped while borrowed
println!("{}", r);

A method call in between breaks it too

as_str borrows the temporary String. But the String itself is dropped at the end of the statement. That leaves text pointing at freed memory. Rust refuses:

let text = String::from("hello world").as_str(); // error E0716
// fix: give the String a name first, then borrow from the name

A match keeps its temporaries for the whole match

The String here is a temporary. Yet the arm may borrow a word from it. Scrutinee temporaries live until the match finishes. They do not die at the first line:

match String::from("red blue").split(' ').next() {
    Some(word) => println!("first word = {}", word),
    None => println!("none"),
}

Constants can hold a temporary forever

A const can be built from constants alone. Its temporary then gets promoted to 'static. It lives for the whole program:

const EMPTY: &Vec<i32> = &Vec::new();
println!("len = {}", EMPTY.len());

Example

fn main() {
    let greeting = &String::from("hello");
    println!("{}", greeting);
    let nums = &vec![10, 20, 30];
    println!("second = {}", nums[1]);
    let boxed = Some(&String::from("inside"));
    println!("{:?}", boxed);
}

Key points

  • Temporary: a value with no name of its own, made in the middle of an expression
  • The default rule: a temporary is dropped at the end of the statement that made it
  • Lifetime extension: a let can bind a reference to a temporary.
  • The binding may be direct.
  • It may also sit inside a tuple, struct, array or Some(...).
  • The temporary then lives until the end of the block instead
  • Extension stops at any function call or method call in between: identity(&temp()) and temp().as_str() drop the temporary at the semicolon, and using the borrow afterwards is error E0716
  • A match is special.
  • Temporaries in the value being matched live until the whole match is over.
  • So arms may borrow from them freely
  • Static promotion: a temporary can be promoted to live for the whole program.
  • It must be built only from constants, like the Vec in a const

In this example, three nameless values outlive their statements. greeting borrows a temporary String: lifetime extension keeps the String alive until the end of main, so hello prints. nums borrows a temporary Vec the same way, and second = 20 prints. boxed wraps a third temporary inside Some(...), and the stretch reaches through the wrapper, printing Some("inside"). None of the three values was ever given a name of its own. The borrows in the let lines are what kept them alive.

Note

One edge changed in the 2024 edition.

A temporary made in an if let condition now drops earlier.

It drops before the else block runs.

On the 2021 edition it stays alive until the whole if let is finished.

That kind of detail decides when a guard or lock is released.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Why does `let text = String::from("hi there").as_str();` fail with error E0716?