Trying to understand a lifetime error (E0597)

I've been using Rust for a few months, and today I hit a compiler error I wasn't expecting and cannot explain. I wondered if I could get some help figuring out what is going on. Basically I have a HashSet protected by a Mutex. I am attempting to pull all the values out of the set and place them in a Vec. My first approach looked something like this (Playground link):

let locked_set = Mutex::new(HashSet::<u32>::new());
let _v = {
    let mut guard = locked_set.lock().unwrap();
    guard.drain().collect::<Vec<_>>()
};

However, this complains that guard does not live long enough. If I change the block to use a temporary variable, it compiles (Playground link):

let _v = {
    let mut guard = locked_set.lock().unwrap();
    let v = guard.drain().collect::<Vec<_>>();
    v
};

Could anyone explain what makes these two not equivalent from a lifetime perspective?

3 Likes

This is some artifact of how lexical lifetimes interact with temporaries in expressions.

It is fixed by NLL: playground

1 Like

You probably meant to share an NLL-enabled playground of the first code snippet.

2 Likes

I sure did - that’s what I get for being on mobile :slight_smile:

Link fixed (I think) - thanks for pointing that out @HadrienG!

Thanks @vitalyd for noting that an improvement is coming! Your answer also helped me improve my searching, which got me to the right place in the language reference, which states (emphasis mine):

... the 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 ...

along with with this reddit thread which discusses the issue in much more detail. That thread in particular discusses some cases in which this rule is necessary, but also notes that this is an issue that multiple users been bitten by unexpectedly.

3 Likes

It’s interesting how the reddit thread somewhat disagrees that it’s a bug; IMO, it is :slight_smile:. The compiler has enough information to see whether the rvalue borrow needs to extend just beyond the enclosing block. Perhaps we can call it an impl limitation (prior to NLL), but I don’t think it’s a “feature”.

It would also be interesting to see how the examples in that thread fair with NLL.

2 Likes