How to fix temporary value dropped while borrowed

Fix the temporary value dropped error by assigning the temporary value to a variable before borrowing it.

The temporary that vanished

You write a function to parse a configuration line. You call it with a freshly formatted string: parse(&format!("user=admin")). The compiler rejects you. It complains about a temporary value being dropped while borrowed. You stare at the code. The string exists right there. You can see the data. Rust insists it's gone.

This is E0716. The error message is blunt: "temporary value dropped while borrowed." It happens when you create a value, borrow it, and try to keep the reference after the value's lifetime ends. Rust's borrow checker catches this at compile time. It prevents you from holding a reference to memory that has already been freed.

Temporaries and the statement boundary

Rust tracks the lifetime of every value. A borrow requires the owner to outlive the borrow. If you borrow a value, that value must stay alive for as long as the reference exists.

A temporary value is a value with no name. It's created by an expression like String::from("hello"), format!("..."), or a function call that returns an owned type. Because it has no name, Rust assigns it a default lifetime. That lifetime ends at the statement boundary.

The statement boundary is usually the semicolon. When the compiler reaches the semicolon, it drops all temporaries created in that statement. If you borrowed a temporary and assigned the reference to a variable, that variable now points to dropped memory. Rust forbids this. The compiler stops you before the code runs.

Think of a temporary like a disposable coffee cup handed to you at a kiosk. You take the cup, you use it for the transaction, and the moment the transaction ends, the cup vanishes. If you try to hand that cup to a friend to hold while you walk away, they're holding nothing. Rust enforces this strictly. The cup disappears the moment the statement finishes.

Minimal example: The crash course

This example shows the error in its simplest form. The function returns a reference into the string it receives. The caller passes a temporary string.

/// Returns a reference to the first word in the string.
fn get_first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    // Scan for the first space to find the word boundary.
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            // Return the slice up to the space.
            return &s[..i];
        }
    }
    // No space found, return the whole string.
    s
}

fn main() {
    // This line triggers E0716.
    // String::from creates a temporary String on the heap.
    // The & borrows that temporary.
    // get_first_word returns a reference pointing into the temporary.
    // The semicolon ends the statement.
    // The temporary String is dropped immediately.
    // The variable `word` would hold a dangling reference.
    let word = get_first_word(&String::from("hello world"));
}

Bind the temporary to a named variable. This extends the lifetime to the end of the scope. The borrow checker allows the reference to live as long as the variable exists.

fn main() {
    // Bind the temporary to a named variable.
    // This gives the String a name and extends its lifetime
    // to the end of the main function scope.
    let text = String::from("hello world");
    // Now the borrow is valid.
    // `text` lives longer than `word`.
    let word = get_first_word(&text);
    println!("{}", word);
}

Bind the variable. Give the value a home. The borrow checker rewards ownership.

Walkthrough: What the compiler sees

The compiler analyzes the code in phases. It builds a graph of values, borrows, and lifetimes. When it sees String::from("hello world"), it marks the result as a temporary with no binding. It sees the & and records a borrow of that temporary. It sees the call to get_first_word and records that the function returns a reference tied to the borrow.

The compiler reaches the semicolon. It checks the lifetime of the temporary. The temporary's lifetime ends here. It checks the reference returned by the function. The reference is assigned to word, which lives until the end of the scope. The reference outlives the temporary. This is a use-after-free waiting to happen. The compiler emits E0716.

The error points to the borrow. It tells you exactly which temporary is the problem. The fix is to extend the lifetime of the temporary. You do this by binding it to a variable. A named variable lives until the end of its scope, not just the end of the statement.

Realistic example: Config parsing

This pattern appears often in real code. You read a line from a file or a buffer. You format it or transform it. You pass it to a parser. The parser returns a reference to a part of the string.

/// Extracts the key from a "key=value" string.
fn extract_key(line: &str) -> &str {
    // Split on equals and take the first part.
    line.split('=').next().unwrap_or("")
}

fn main() {
    // This fails with E0716.
    // format! creates a temporary String.
    // extract_key returns a reference into that temporary.
    // The temporary dies at the semicolon.
    let key = extract_key(&format!("user=admin"));
}

The fix is the same. Bind the formatted string to a variable. The variable keeps the string alive.

fn main() {
    // Bind the formatted string to a variable.
    // This extends the lifetime to the end of the scope.
    let config_line = format!("user=admin");
    // The borrow is now valid.
    let key = extract_key(&config_line);
    println!("Key: {}", key);
}

Fix the lifetime by fixing the ownership. Bind the variable, or change the return type.

The if let exception

Rust has a special rule for control flow. Temporaries created in the condition of an if let or while let expression are extended to the end of the block. This lets you write concise code without binding variables manually.

fn main() {
    // This works.
    // The temporary String lives for the entire block.
    // The reference `word` is valid inside the block.
    if let Some(word) = get_first_word(&String::from("hello world")) {
        println!("First word: {}", word);
    }
    // The temporary is dropped here, after the block ends.
}

The compiler extends the temporary's lifetime to cover the block. This is a deliberate design choice. It allows patterns like if let Some(x) = vec![1, 2, 3].iter().next() to work safely. The vector lives for the block. The iterator borrows it. The reference is valid inside the block.

This exception only applies to if let and while let. It does not apply to plain if or match. If you need the reference outside the block, bind the temporary to a variable first.

Control flow extends temporaries. Use it when the reference only lives inside the block.

Pitfalls and error codes

Method chains hide temporaries. A chain like vec![1, 2, 3].iter().next() creates a temporary vector. The iterator borrows it. next returns a reference. The vector drops at the end of the statement. The reference dangles. This fails with E0716.

fn main() {
    // This fails.
    // vec! creates a temporary Vec.
    // iter() borrows the temporary.
    // next() returns Option<&i32>.
    // The Vec drops at the semicolon.
    // The reference inside the Option is dangling.
    let first = vec![1, 2, 3].iter().next();
}

Bind the vector to a variable. The variable keeps the vector alive.

fn main() {
    let numbers = vec![1, 2, 3];
    let first = numbers.iter().next();
}

The community treats &format!(...) as a code smell. It allocates a string just to borrow it. If you need a reference, you likely have a lifetime design problem. If you need the string, drop the &. Passing &format! usually means you're fighting the borrow checker. Step back and check if you can return an owned type instead.

The compiler rejects these cases with E0716 (temporary value dropped while borrowed). The error message includes the type of the temporary and the location of the borrow. Read the error carefully. It tells you exactly what needs to be bound.

E0716 is a guardrail. It stops you from dereferencing freed memory. Respect it.

Decision matrix

Use a named variable when the temporary must outlive the current statement. Bind the value first, then borrow it. This extends the lifetime to the end of the scope.

Return an owned type when the function creates data that needs to survive the call. Return String instead of &str if the result comes from a temporary or local computation. This avoids lifetime issues entirely.

Use Cow<str> when you want to accept both owned and borrowed strings and return a reference when possible. This avoids allocation when the input is already borrowed, but handles temporaries gracefully by cloning into owned data.

Reach for String::from or .to_string() when you need a heap-allocated string that you control. This gives you ownership, so you can borrow it as long as you hold the variable.

Pick &'static str when the data is known at compile time. String literals have a static lifetime. They never drop. You can borrow them anywhere without temporary issues.

Choose the tool that matches the lifetime you need. Ownership beats borrowing when things get messy.

Where to go next