Search code examples
closuresrustmutablelifetime

Lifetime problems passing &mut to function and returning a closure


I have a short example of incrementing a vector by divide and conquer. Very basic, I just can't get the lifetimes correct. I'm pretty sure it has to do with &'s mut argument lifetime and the TaskResult<'s> return lifetime, but I'm not sure how to make it work.

The code on the playpen

fn main() {
    let mut data = vec![1,2,3,4,5,6,7,8,9];
    let t = inc_vec(data.as_mut_slice());
}

pub type MyClosure<'s> = FnMut() -> TaskResult<'s> + Send + 's;

pub enum TaskResult<'s> {
    Done(usize),
    Fork(Vec<Box<MyClosure<'s>>>),
}

fn inc_vec<'s>(data: &'s mut [usize]) -> TaskResult {
    if data.len() <= 4 {
        inc_vec_direct(data)
    } else {
        inc_vec_fork(data)
    }
}

fn inc_vec_fork<'s>(data: &'s mut [usize]) -> TaskResult<'s> {
    let mid = data.len()/2;
    let (l,r) = data.split_at_mut(mid);

    let task_l: Box<MyClosure<'s>> = Box::new(move || {
        inc_vec(l)
    });
    let task_r: Box<MyClosure<'s>> = Box::new(move || {
        inc_vec(r)
    });

    TaskResult::Fork(vec![task_l, task_r])
}

fn inc_vec_direct(data: &mut [usize]) -> TaskResult {
    for d in data {
        *d += 1;
    }
    TaskResult::Done(1)
}

And it gives me the following error (truncated since the same error is produced twice, once for task_l and once for task_r):

src/main.rs:26:17: 26:18 error: cannot infer an appropriate lifetime for automatic coercion due to conflicting requirements
src/main.rs:26         inc_vec(l)
                               ^
src/main.rs:25:55: 27:6 note: first, the lifetime cannot outlive the lifetime  as defined on the block at 25:54...
src/main.rs:25     let task_l: Box<MyClosure<'s>> = Box::new(move || {
src/main.rs:26         inc_vec(l)
src/main.rs:27     });
src/main.rs:26:17: 26:18 note: ...so that closure can access `l`
src/main.rs:26         inc_vec(l)
                               ^
src/main.rs:21:62: 33:2 note: but, the lifetime must be valid for the lifetime 's as defined on the block at 21:61...
src/main.rs:21 fn inc_vec_fork<'s>(data: &'s mut [usize]) -> TaskResult<'s> {
src/main.rs:22     let mid = data.len()/2;
src/main.rs:23     let (l,r) = data.split_at_mut(mid);
src/main.rs:24
src/main.rs:25     let task_l: Box<MyClosure<'s>> = Box::new(move || {
src/main.rs:26         inc_vec(l)
               ...
src/main.rs:25:38: 27:7 note: ...so that trait type parameters matches those specified on the impl (expected `TaskResult<'_>`, found `TaskResult<'s>`)
src/main.rs:25     let task_l: Box<MyClosure<'s>> = Box::new(move || {
src/main.rs:26         inc_vec(l)
src/main.rs:27     });

There must be a simple fix to this. All I want to say is that I return a vector of closures that have mutable references to parts of the input slice. I guess I have to mark the closure lifetime as shorter than the data slice lifetime, just not sure how to do that.


Solution

  • You can get your example to compile and run if you change one line:

    pub type MyClosure<'s> = FnOnce() -> TaskResult<'s> + Send + 's;
    //                       ^~~~~~
    

    I'm still thinking through how to explain it though!

    This is the code I was starting from. I made a few simplifications to get started, mostly around removing the lifetime references where they aren't needed. Lifetime elision means that fn(foo: &T) -> &U is the same as fn<'a>(foo: &'a T) -> &'a U, but not the same as fn<'a>(foo: &'a T) -> &U.

    fn main() {
        let mut data = vec![1,2,3,4,5,6,7,8,9];
        let t = inc_vec(data.as_mut_slice());
    }
    
    pub type MyClosure<'s> = FnMut() -> TaskResult<'s> + Send + 's;
    
    pub enum TaskResult<'s> {
        Done(usize),
        Fork(Vec<Box<MyClosure<'s>>>),
    }
    
    fn inc_vec(data: &mut [usize]) -> TaskResult {
        if data.len() <= 4 {
            inc_vec_direct(data)
        } else {
            inc_vec_fork(data)
        }
    }
    
    fn inc_vec_fork(data: &mut [usize]) -> TaskResult {
        let mid = data.len() / 2;
        let (l, r) = data.split_at_mut(mid);
    
        let task_l: Box<MyClosure> = Box::new(move || inc_vec(l));
        let task_r: Box<MyClosure> = Box::new(move || inc_vec(r));
    
        TaskResult::Fork(vec![task_l, task_r])
    }
    
    fn inc_vec_direct(data: &mut [usize]) -> TaskResult {
        for d in data { *d += 1; }
        TaskResult::Done(1)
    }
    

    Mostly, I got to the result by changing the closure just a smidge:

    let task_l: Box<MyClosure> = Box::new(move || { let a = l; inc_vec(a)});
    

    Which should be the same code. However, this has the error:

    error: cannot move out of captured outer variable in an `FnMut` closure
         let task_l: Box<MyClosure> = Box::new(move || { let a = l; inc_vec(a)});
                                                                 ^
    note: attempting to move value to here
         let task_l: Box<MyClosure> = Box::new(move || { let a = l; inc_vec(a)});
                                                             ^
    help: to prevent the move, use `ref a` or `ref mut a` to capture value by reference
    

    Which led me to try the various Fn* traits, with FnOnce working. I think that the solution boils down to the fact that Rust disallows aliasing of mutable references (a.k.a. you can't point to the same mutable thing twice). If you had a FnMut or Fn, then you could call the closure multiple times, which would give an opportunity to create aliases. It'd be awful nice if the error message included anything about mutability though!

    A FnOnce is guaranteed to only be called once, which prevents that particular aliasing opportunity.

    I think you could file 1 or 2 bugs from this:

    1. It's surprising that the error message changes based on if there is a let or not.
    2. It would be nice to mention mutability as the reason that the variable can't be moved into the closure.