Problem extracting value from an Option<Arc<Mutex<&mut dyn T>>>

I found an example where it seems like something that should work in Rust simply doesn't, leading to what feels like needlessly complicated code.

Here's a playground link: Rust Playground

Here's the code:

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.

2 Likes

Alternatively make fiddle_with_thingy more general.

fn fiddle_with_thingy(_thingy: Option<&mut (dyn DynThingy + '_)>) {
    // ...
}
// ...
        let mut bar = self.bar.as_deref().map(|ap| ap.lock().unwrap());
        let bar = bar.as_deref_mut();
        fiddle_with_thingy(bar);
2 Likes

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.

Thanks for the help! Where can I learn more about &mut (dyn T + '_) ? I'd like to learn more about lifetimes of dyn references.

Read following sections from The Rust Reference:

1 Like

Here's my guide on the topic.

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


  1. 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. ↩︎

  2. you can pass in a T = &'local mut (dyn DynThingy + 'static) and get out a U = &'local mut (dyn DynThingy + 'local) ↩︎

1 Like