3

This playground project has a simplified version of the code I'm trying to compile.

use std::sync::{Arc, Mutex, MutexGuard};

pub trait Runnable {
    fn run(&mut self) -> Option<String>;
}

pub struct Value {}

impl Runnable for Value {
    fn run(&mut self) -> Option<String> {
        Some("Value".to_string())
    }
}

pub struct RunList {
    runnables: Vec<Arc<Mutex<Runnable>>>,
}

impl RunList {
    pub fn run<R>(&mut self, index: usize, mut runner: R)
    where
        R: FnMut(&mut MutexGuard<Runnable>),
    {
        let runnable_arc = self.runnables[index].clone();
        let mut runnable = runnable_arc.lock().unwrap();
        runner(&mut runnable);
    }
}

fn main() {
    let mut runnables = Vec::<Arc<Mutex<Runnable>>>::with_capacity(1);
    runnables.push(Arc::new(Mutex::new(Value {})));

    let mut run_list = RunList { runnables };

    run_list.run(0, |runnable| {
        println!("Hello, {}", runnable.run().unwrap());
    });
}

I want a vector of trait objects, where each object is protected by an Arc and a Mutex, and then to be able to call a trait method on each object.

I have a "borrowed value does not live long enough" error but I can't see the difference from this question/answer.

error[E0597]: `runnable_arc` does not live long enough
  --> src/main.rs:25:28
   |
25 |         let mut runnable = runnable_arc.lock().unwrap();
   |                            ^^^^^^^^^^^^ borrowed value does not live long enough
26 |         runner(&mut runnable);
27 |     }
   |     - borrowed value only lives until here
   |
   = note: borrowed value must be valid for the static lifetime...
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Andrew Mackenzie
  • 5,477
  • 5
  • 48
  • 70
  • The error given by your simplified playground code is not the same error that you've posted in the question. Perhaps your playground example has been simplified too much? – loganfsmyth Mar 10 '18 at 23:03
  • `R: FnMut(&mut MutexGuard)` is the same as `R: FnMut(&mut MutexGuard)`. You want `R: for <'a> FnMut(&mut MutexGuard)` – Shepmaster Mar 11 '18 at 05:21
  • 2
    @Shepmaster If you write `R: FnMut(&mut MutexGuard)` though, the code compiles fine. – Francis Gagné Mar 11 '18 at 06:10
  • @Francis Cagné indeed it does! Now I'll go and study why! :-) If you want to submit an answer I'll accept it. If not, thanks anyway! – Andrew Mackenzie Mar 11 '18 at 10:12
  • The problem was wierder than I expected. In my example I stripped out a later call after "runner(...)". In my real code it continued to fail until I did a deref on that code line which is AFTER the line the compiler was reporting the error on. I'll probably post a new playground example to illustrate when I get a moment. Adding a deref to the *subsequent* line fixed it! – Andrew Mackenzie Mar 11 '18 at 11:08
  • @FrancisGagné interesting — reopened. – Shepmaster Mar 11 '18 at 15:18

2 Answers2

4

You seem to be running into a glitch in the compiler's inference system. Changing MutexGuard<Runnable> to MutexGuard<Runnable + 'static> fixes the error:

impl RunList {
    pub fn run<R>(&mut self, index: usize, mut runner: R)
    where
        R: FnMut(&mut MutexGuard<Runnable + 'static>),
    {
        let runnable_arc = self.runnables[index].clone();
        let mut runnable = runnable_arc.lock().unwrap();
        runner(&mut runnable);
    }
}

But interestingly, changing it to MutexGuard<'static, Runnable + 'static> preserves the error:

impl RunList {
    pub fn run<R>(&mut self, index: usize, mut runner: R)
    where
        R: FnMut(&mut MutexGuard<'static, Runnable + 'static>),
    {
        let runnable_arc = self.runnables[index].clone();
        let mut runnable = runnable_arc.lock().unwrap();
        runner(&mut runnable);
    }
}

Why would the compiler infer the latter rather than the former? Well, let's look at the definition of MutexGuard:

pub struct MutexGuard<'a, T: ?Sized + 'a> { /* fields omitted */ }

In MutexGuard<Runnable>, Runnable is a trait object and gets an implicit + 'static bound because there are no explicit lifetimes. It seems that the compiler tries to unify both occurrences of 'a by reasoning that, if we need to replace the second occurrence with 'static, then we need to do the same with the first occurrence. However, that logic forgets an important detail: 'static outlives any lifetime 'a, so there's no reason to force the first 'a to be 'static. Indeed, when we write MutexGuard<Runnable + 'static>, that's the same as MutexGuard<'a, Runnable + 'static> (how 'a is defined depends on the context). That's valid because Runnable + 'static: 'a (you can always pass an object with some lifetime 'x as an argument to a function that expects a shorter lifetime 'y).


For the record, the issue has nothing to do with the FnMut trait; a normal function also shows this behavior:

impl RunList {
    pub fn run(&mut self, index: usize) {
        fn runner2(runnable: &mut MutexGuard<Runnable>) {
            println!("Hello, {}", runnable.run().unwrap());
        }

        let runnable_arc = self.runnables[index].clone();
        let mut runnable = runnable_arc.lock().unwrap();
        runner2(&mut runnable);
    }
}
Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
2

You are passing a reference to the MutexGuard to your runner instead of a reference to the wrapped runnable. You need to insert a deref:

pub fn run<R>(&mut self, index: usize, mut runner: R)
where
    R: FnMut(&mut Runnable),
{
    let runnable_arc = self.runnables[index].clone();
    let mut runnable = runnable_arc.lock().unwrap();
    runner(&mut *runnable);
}

From my understanding, this construct - &mut * - shouldn't be necessary here, because MutexGuard implements the DerefMut trait. Apparently, this is a known issue: Why does a mutable borrow of a closure through DerefMut not work?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Calculator
  • 2,769
  • 1
  • 13
  • 18
  • I understand that since MutexGuard implements Deref and DerefMut, that is done for you by the compiler. https://doc.rust-lang.org/std/sync/struct.MutexGuard.html Although, I actually prefer to make things more explicit and quite like your suggestion. – Andrew Mackenzie Mar 11 '18 at 10:18