Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I get "does not live long enough" in a return value?

In examining How do I box Arc and Mutexed variables?, I ran into an issue where code that looks OK is generating a "does not live long enough" error while constructing a return value from a Mutex. Simply pulling the lock().unwrap() access out of the return object removes the error - but I'd like to understand why Rust is complaining about a lifetime issue in this case.

I was able to cut the code down to a very simple reproducer: The first function compiles OK, the second generates the error message, and they're almost identical.

use std::sync::Mutex;

pub struct Response {
    resp: String,
}

pub fn get() -> Response {
    let body = Mutex::new("a".to_string());
    let x: std::sync::MutexGuard<_> = body.lock().unwrap();
    Response { resp: x.clone() }
}

pub fn get2() -> Response {
    let body = Mutex::new("a".to_string());
    Response {
        resp: body.lock().unwrap().clone(),
    }
}
error[E0597]: `body` does not live long enough
  --> src/lib.rs:16:15
   |
16 |         resp: body.lock().unwrap().clone(),
   |               ^^^^ borrowed value does not live long enough
17 |     }
18 | }
   | - `body` dropped here while still borrowed
   |
   = note: values in a scope are dropped in the opposite order they are created
like image 234
Michael Anderson Avatar asked Dec 03 '18 01:12

Michael Anderson


2 Answers

As pointed out in Stargateur's answer, the reason for this is the lifetime of temporaries. While Rust does not have a full specification yet, the language reference still is quite good and at least gives a hint to understand the behaviour. Here is the relevant part from the section about temporary lifetimes:

[T]he lifetime of temporary values is typically

  • the innermost enclosing statement; the tail expression of a block is considered part of the statement that encloses the block, or
  • the condition expression or the loop conditional expression if the temporary is created in the condition expression of an if or in the loop conditional expression of a while expression.

The innermost enclosing statement of body.lock().unwrap() in the second version of your function is the return expression. The spec above states that this expression is "considered part of the statement that encloses the block", which doesn't really exist in this case, but it still gives the right idea: All variables local to the function body are dropped before any temporaries in the return expression is dropped, so body is dropped before the MutexGuard that borrows body. The fix you found makes sure the temporary is dropped before body, since local variables are dropped roughly in reverse order of their creation.

like image 89
Sven Marnach Avatar answered Oct 31 '22 14:10

Sven Marnach


Using #![feature(nll)] this give a precise error:

   |
19 |         resp: body.lock().unwrap().clone(),
   |               ^^^^----------------
   |               |
   |               borrowed value does not live long enough
   |               a temporary with access to the borrow is created here ...
20 |     }
21 | }
   | -
   | |
   | `body` dropped here while still borrowed
   | ... and the borrow might be used here, when that temporary is dropped and runs the `Drop`
   | code for type `std::sync::MutexGuard`
   |
   = note: The temporary is part of an expression at the end of a block. Consider forcing
   this temporary to be dropped sooner, before the block's local variables are dropped.
   For example, you could save the expression's value in a new local variable `x` and then make
   `x` be the expression at the end of the block.

Quite long BTW ;) It's because the temporary is drop after body, you could also do this:

pub fn get3() -> Response {
    let body = Mutex::new("a".to_string());
    let resp = body.lock().unwrap().clone();
    Response {
        resp,
    }
}
like image 5
Stargateur Avatar answered Oct 31 '22 16:10

Stargateur