Temporary lifetimes woes


#1

As explained in the reference, the lifetime of a temporary variable is the enclosing statement. I find this surprising and annoying, and am curious as to the justification. My annoyance arises from having introduced a deadlock into my code through this lifetime practice. As I’ve written this message, I’ve come to a guess as to the reasoning behind this, but am hoping the message will be helpful to others. At a minimum, the rust book should have some mention of how lifetimes work in expressions. After all, it’s got a whole section on lifetimes, which seems to be a natural place for newbies to go!

I would prefer for the following two expressions equivalent:

let a = f(expr1, expr2);
let b = { let arg1 = expr1; let arg2 = expr2; f(arg1,arg2) };

Sadly, they are not, and in a way that can be very frustrating. Specifically, the lifetimes of every intermediate computed in these expressions is the same as the lifetime of the expression. I can imagine this simplifies the borrow checker considerably, but it introduces pitfalls that I did not expect in a call-by-value language.

My error case was in the use of a Mutex, where I thought that it would be good to make the taking of the lock explicit, so I could see where it was done in the code. This resulted in a situation where I wrote code such as:

function_that_blocks(m.lock().unwrap().method_that_cannot_block());

I thought this could not block while holding the lock, since the lock was only taken “for” the method that cannot block, which itself returns a simple value that . Now I know that I cannot use locks in this way, and that I should instead either bind separately to a let with each taking of a lock, or I should hide the lock-taking inside a separate function (or method) so that the guard cannot exposed like this.

I wonder, would it be possible to make the lifetime of a reference passed to a function always the same as the block defining the function, provided the function does not have an output sharing that lifetime? It would add complexity, but would be much closer to a “do what I mean” behavior for temporaries. And it could save memory too!

Sadly, I think changing this (as changing anything involving lifetimes) would be a serious breaking change. On the plus side, shortening lifetimes is probably safer than lengthening them, since the borrow checker will catch many cases where they are shortened beyond what was expected. The exception is when Drop is in play, in which case shortening lifetimes could have any number of evil results.


#2

It’s not the borrow checker per se; as you mention, the issue is with calling methods whose output lifetime is tied to their input. C++ has the same rule for the same reason.

Making behavior depend on whether the method actually has such a lifetime would have advantages and disadvantages, but - also as you note - it’s a breaking change, so highly unlikely to happen anytime soon.