use std::sync::{Arc, Mutex};
trait DynThingy {
// ... stuff ...
}
struct Foo {
bar: Option<Arc<Mutex<dyn DynThingy>>>,
}
fn fiddle_with_thingy(_thingy: Option<&mut dyn DynThingy>) {
// ...
}
impl Foo {
fn fiddle_with_bar(&self) {
let bar = self.bar.as_deref().map(|ap| ap.lock().unwrap());
// NOPE, you can't do this!
// let bar = bar.map(|mut ap| &mut *ap);
// fiddle_with_thingy(bar);
// INSTEAD, you have to do this...
match bar {
Some(mut bar) => fiddle_with_thingy(Some(&mut *bar)),
None => fiddle_with_thingy(None)
}
}
}
The comments explain the problem. I would like to use the former code here, but the compiler fails with this error:
Compiling playground v0.0.1 (/playground)
error[E0515]: cannot return value referencing function parameter `ap`
--> src/lib.rs:20:36
|
20 | let bar = bar.map(|mut ap| &mut *ap);
| ^^^^^^--
| | |
| | `ap` is borrowed here
| returns a value referencing data owned by the current function
error: lifetime may not live long enough
--> src/lib.rs:17:48
|
16 | fn fiddle_with_bar(&self) {
| - let's call the lifetime of this reference `'1`
17 | let bar = self.bar.as_deref().map(|ap| ap.lock().unwrap());
| ^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'static`
For more information about this error, try `rustc --explain E0515`.
error: could not compile `playground` (lib) due to 2 previous errors
It isn't clear to me why there would be a borrow-checker error. Is there a way to use Option::map the way I intended?
You can't transform the lock, you have to borrow it and transform the borrow.
let mut bar = self.bar.as_deref().map(|ap| ap.lock().unwrap());
let bar = bar.as_mut().map(|ap| { &mut **ap });
fiddle_with_thingy(bar);
The braces in the closure are needed due to what is arguably a bug. Without it you get a &mut (dyn 'static + ...) and you need thedyn lifetime to be non-'static.
Rustfmt removes the braces so you may want to use as instead.
my first reaction was "it's just as_deref_mut()", then I was confused it didn't work either. totally forgot about the default 'static bound of trait objects.
There are also a lot of little details that came together in this example, so I'll point out a few more specific things.
One part is that Mutex<dyn DynThingy> is really a Mutex<dyn DynThingy + 'static> in your struct, and &mut dyn DynThingy is really a &'d mut (dyn DynThingy + 'a) in the args to fiddle_with_thingy -- note how the two lifetimes are the same. Here's my section about that part.[1]
Another part is that as_deref, as_deref_mut, and lock all preserve the full inner type here, so the + 'static was preserved.
// For example, here's what `lock` looks like:
impl<T: ?Sized> Mutex<T> {
// ^-----------------------------v
pub fn lock(&self) -> LockResult<MutexGuard<'_, T>> { ... }
// In your case, `T` is `dyn DynThiny + 'static`.
}
So you end up with an Option<&'local mut (dyn DynThingy + 'static)>, and when passed to the signature that requires the two lifetimes be the same, the compiler reports you would have to have a &'static mut. Loosening the signature so the two lifetimes can be different solves that.
map doesn't have to preserve the inner type,[2] but due to that issue I linked, in practice it was does preserve the inner type when you don't use braces. When you use braces, there's a chance for coercion, and you can get a &'local mut (dyn DynThingy + 'local).
But then you may wonder why the Option<..> you passed doesn't coerce. And the reason for that is because that &mut dyn coercion is actually an unsizing coercion that can't happen in a nested context like under a &mut in an Option. (On the other hand it's special in that it can shrink a lifetime under a &mut at all.)
As for the reference, it has inaccuracies on the topic of dyn lifetime elision, but they don't apply to this example. Mutex<_> acts the same as Box<_>, which is used in the reference's examples. ↩︎
you can pass in a T = &'local mut (dyn DynThingy + 'static) and get out a U = &'local mut (dyn DynThingy + 'local)↩︎