[Question]: E0716 - temporary value dropped while borrowed: Results and Options

I've been looking at the solutions to E0716 - temporary value dropped while borrowed.

It looks like the solution usually boils down to inappropriate method chains. For example:

let val = expr.clone().gettype();

This won't work because the value of expr.clone() isn't saved anywhere, so gettype() has nothing to act on.

But if this is the case, why do method chains work when we're dealing with things like Options and Results?

For example: why does this code work?

#[derive(Debug)]
pub enum ExError {
    Errr
}

fn main () {
    let num: u64 = 20;
    let result_num = gimme_a_result(num).unwrap();
    println!("{}", result_num);
}

fn gimme_a_result(input: u64) -> Result<u64, ExError> {
    Ok(input)
}

Why don't we need to write it like this when we're dealing with Option and Result?

#[derive(Debug)]
pub enum ExError {
    Errr
}

fn main () {
    let num: u64 = 20;
    let raw_result = gimme_a_result(num);
    let result_num = raw_result.unwrap();
    println!("{}", result_num);
}

fn gimme_a_result(input: u64) -> Result<u64, ExError> {
    Ok(input)
}

Because no referene is provided, only owned values, and no temporary is created. For example, this code will fail (playground):

fn main() {
    let v = foo(&"abc".to_owned()).unwrap();
    dbg!(v);
}

fn foo(s: &str) -> Option<&str> { Some(s) }
error[E0716]: temporary value dropped while borrowed
 --> src/main.rs:2:18
  |
2 |     let v = foo(&"abc".to_owned()).unwrap();
  |                  ^^^^^^^^^^^^^^^^          - temporary value is freed at the end of this statement
  |                  |
  |                  creates a temporary which is freed while still in use
3 |     dbg!(v);
  |          - borrow later used here
  |
  = note: consider using a `let` binding to create a longer lived value

Nice, just to make sure I'm understanding this right:

  • Temporary values are created when you call a method on a reference or return a reference from a function.
  • If temporary values are not assigned to a variable, then you can't use them.
  • Temporary values need to be assigned to a variable so that they are owned by something. That way, Rust won't drop the value

No. A temporary is any value that isn't bound to a variable/pattern (i.e. what other languages call an rvalue).

Also no. You can use a temporary just fine. However, no value (temporary or bound) is allowed to be referenced longer than its own lifetime.

This isn't technically correct, either – the reason for that is sort of implied by the previous statement. You don't need to assign a temporary to a variable, you can use it (including taking a reference to it) as long as you uphold all the usual borrowing & lifetime rules.

1 Like

Following the explanations in the Rust Reference, temporaries are created in particular if a value (expression) is used in a “place expression context”.

Temporaries

When using a value expression in most place expression contexts, a temporary unnamed memory location is created and initialized to that value. The expression evaluates to that location instead […]. The drop scope of the temporary is usually the end of the enclosing statement.


Regarding

nomenclature-wise, whether or not every value that isn’t bound to a variable gets a “temporary”, can be debated. AFAICT, the reference only talks of temporaries in the above case, as well as here

Operands

Temporaries are also created to hold the result of operands to an expression while the other operands are evaluated. […] Since the temporaries are moved from once the expression is evaluated, dropping them has no effect unless one of the operands to an expression breaks out of the expression, returns, or panics.

so perhaps in simple cases like foo(bar(baz)), you might even consider no temporary being created at all for the return value of bar(baz) that’s passed to foo. (Not that it would make a difference though.)


In any case, as mentioned above, the interesting/main case for temporaries is that of turning a value into a place! E.g. in @chrefr's example statement,

let v = foo(&"abc".to_owned()).unwrap();

there is the value "abc".to_owned() as part of the expression &"abc".to_owned(), but &… is a so-called place expression context. If you write &foo, you create a reference to the place where foo lives. Thus for evaluating &"abc".to_owned(), the String value created by evaluating "abc".to_owned() is placed into a temporary, and that temporary is the place the reference refers to.

Temporaries have a scope, typically the enclosing statement, so after the let v = …; statement is done being evaluated, all its temporaries, including the one containing the String containing the "abc".to_owned(), are going out of scoped / are dropped. This scope limits the lifetime of the reference that was created by &"abc".to_owned() and passed to foo. The type signature of foo relates that lifetime to the lifetime of the reference type in the type of its Option<&str> return value, and .unwrap() hence returns a &str whose lifetime is still limited by the scope of the temporary.

This means that that &str that was assigned to v cannot be used after the let v = …; statement, because it’s become a dead reference at that point.

The compiler tracks all this lifetime-interdependency and explains this conflict by pointing directly to the root of the limitation (the temporary being dropped at the end of its scope) on one hand, and the use of the derived value v after the end of that scope on the other hand. Figuring out the exact reason why using v constitutes usage of a “borrow” (i.e. shared reference to) the temporary value in question is left to the reader of the error message.


Feel free to click on the headings of the two quotes from the Rust Reference above and read the surrounding information to learn more about temporaries, drop scopes, value expressions vs. place expressions, etc.

2 Likes

In order to relate this information to the quoted examples from other threads:

expr.clone().gettype()

Here, I assume gettype() is a &self method of some type Foo while expr.clone(): Foo is an owned value. Hence the method call desugars to something like Foo::gettype(&expr.clone()), which is the same case as above: Evaluating &expr.clone() needs to put the value created by evaluating expr.clone(), so a temporary is created to hold that value.

Furthermore, I assume that gettype has some kind of function signature fn gettype(&self) -> &Bar or fn gettype(&self) -> Baz<'_>, i.e. the returned value still contains that reference to the temporary (or to a part of the temporary), and the type signature tracks the requirement on lifetimes that this entails. (In case you’re unfamiliar with how to read those function signatures w.r.t. lifetimes, check out this page of the Nomicon on lifetime elision).

So after the statement arraytype = Some(expr.clone().gettype()); is evaluated, the temporary containing the expr.clone() value is dropped, and hence arraytype contains a value that can’t be used anymore. Since it is used, the compiler complains.

error[E0716]: temporary value dropped while borrowed
   --> src/version/interpreter.rs:160:38
    |
160 |                     arraytype = Some(expr.clone().gettype());
    |                                      ^^^^^^^^^^^^           - temporary value is freed at the end of this statement
    |                                      |
    |                                      creates a temporary which is freed while still in use
...
167 |         match arraytype {
    |               --------- borrow later used here



In the other case, there’s less guesswork needed for the function signatures involved. We’d like to understand why in

let n1 = p.clone().unwrap().borrow();

there’s a temporary being created containing p.clone().unwrap(). This expression does evaluate to a value of type Rc<RefCell<TreeNode>>, and the method RefCell::borrow expects &self, i.e. a &RefCell<TreeNode>. There’s also the intermediate Rc<…> to get rid of. Method resolution thus (essentially) desugars to

let n1 = RefCell::borrow(Rc::deref(&p.clone().unwrap()));

meaning that there is a step of creating a reference to p.clone().unwrap(). Or if you want to desugar all the method calls for good measure (and for comparison), it becomes

let n1 = RefCell::borrow(Rc::deref(&Option::unwrap(Clone::clone(p))));

Again, looking at &p.clone().unwrap() (or &Option::unwrap(Clone::clone(p))), as we have an &… expression, and its right-hand-side is a value, not a place, so a temporary is created that the reference can refer to.

The borrow method does not only take &self but also returns a Ref<'_, …> type that has a lifetime connected to the lifetime of &self (due to the fact that it also still contains a reference to the RefCell). And the dereferencing step, Rc::deref is a (&Rc<RefCell<TreeNode>>) -> &RefCell<TreeNode> method, the lifetimes of the input and ouput are also connected.

As another point of comparison, the approach in the solution,

let n1 = p.as_ref().unwrap().borrow();

desugars to

let n1 = RefCell::borrow(Rc::deref(Option::unwrap(Option::as_ref(p))));

The types here work out so that no value needs to be borrowed (or more generally used in any place-expression-context), so no temporary needs to be created. In fact, most intermediate values already are references which are borrowing some other preexisting place. The types of the intermediate results are

p:                                            &Option<Rc<RefCell<TreeNode>>>,

Option::as_ref(p):                            Option<&Rc<RefCell<TreeNode>>>,

Option::unwrap(Option::as_ref(p)):            &Rc<RefCell<TreeNode>>,

Rc::deref(Option::unwrap(Option::as_ref(p))): &RefCell<TreeNode>,

RefCell::borrow(Rc::deref(
    Option::unwrap(Option::as_ref(p))
)):                                           std::cell::Ref<'_, TreeNode>,
1 Like

It seems that what this boils down to is that place expressions (see below) create temporary values.

https://doc.rust-lang.org/stable/reference/expressions.html#place-expressions-and-value-expressions

These temporary values usually only live as long as the enclosing statement in which they're given. And if there's a method chain throwing this error, it likely de-sugars to one of the methods creating a temporary value that needs to be referenced after the method chain is done executing, but can't because it got dropped.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.