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.
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.
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:
let
or not.