I want to present an API that obscures the MutexGuard produced by my type, but I have a lifetime problem that I don't understand. Even if it isn't possible to do what I'm trying to do, I want to understand why so I can avoid this pattern in the future.
Here's the full code, and below is a playground link for convenience
error[E0597]: `engine_guard` does not live long enough
--> src/main.rs:59:26
|
58 | let mut engine_guard = engine.lock();
| ---------------- binding `engine_guard` declared here
59 | let Some(view_mut) = engine_guard.view_mut() else {
| ^^^^^^^^^^^^ borrowed value does not live long enough
...
63 | }
| -
| |
| `engine_guard` dropped here while still borrowed
| borrow might be used here, when `engine_guard` is dropped and runs the destructor for type `EngineGuard<'_>`
The 'guard lifetime is misnamed. It does not refer to anything about the guard, but rather a borrow of the mutex. So, if you successfully returned Option<&'guard mut View>, you would have defeated the mutex and returned a mutable ref that (potentially) outlasted the lock guard.
MutexGuard doesn't return borrows that live longer than itself, so your EngineGuard wrapper can't and shouldn't either. The correct code with full lifetime annotations is:
Thanks, you explained it really well, and it makes a lot of sense. I have never thought about it before, but I think I understand it now: a MutexGuard has the lifetime of the Mutex it belongs to, which upholds the safety invariants, but it doesn't give out references of 'mutex it gives out references of 'guard—its own lifetime.
Once you think about it that way, it just clicks. It was a mental leap for me for sure. I was very far down a rabbit hole where I wrote lifetime relationships such as 'guard: 'view and so on, but that could never help (now that I understand the underlying issue) because when you .lock() a Mutex the lifetime relationship is severed by a little bit of unsafe code IIUC.
Please let me know if any of the above isn't accurate
Also, thank you for your help on wgpu Matrix You're a legend!
Yes, that’s right. The fact that MutexGuard only returns borrows of itself is how it enforces that the use of the value will stop before the mutex is unlocked.
I have one bit of advice for you about how to think about these things:
it gives out references of 'guard —its own lifetime.
I recommend staying far away from phrasings anywhere near “its lifetime”. “the lifetime of this value”. This is because lifetimes never actually denote anything about values, but rather, about borrows.
The lifetime 'guard in &'guard mut self is not “the lifetime of the guard”, but rather the lifetime of a particular borrow of the guard — a particular act of using the &mut operator (whether implicitly or explicitly) to borrow the guard. There can be many different such borrows of the guard (one at a time, of course).
And on the other side, the lifetime 'mutex in EngineGuard<'mutex> is not “the lifetime of the guard” either — it is the lifetime of the borrow of the mutex which produced the reference the guard owns.
If you stick to this principle — never thinking “the lifetime of a value” — you will help yourself think clearly about using lifetimes. When you write a lifetime you're referring to a borrow (or set of borrows), and you need to avoid accidentally conflating two sets that are different.