rust-ndarray / ndarray

ndarray: an N-dimensional array with array views, multidimensional slicing, and efficient operations
https://docs.rs/ndarray/
Apache License 2.0
3.58k stars 304 forks source link

lifetime issues with `axis_iter` #1073

Open jimy-byerley opened 3 years ago

jimy-byerley commented 3 years ago

Hello, that's me again. I apologize for my likely dump questions.

I'm in a situation where I want to process an array slice by slice, where each slice is computed in term of the previous. And my issue is that I cannot achieve this using .reduce instead of a for-loop.

This works:

// iterate slices
let mut it = array.axis_iter_mut(Axis(i));    
let mut last = it.next().unwrap();
for mut current in it {
        // compute 'current' slice in term of the previous 'last' slice
    Zip::from(last).and(&mut current)    
        .for_each(|l, c| {
            *c = /* some expression in term of `l` */;
        });
    last = current;
}

But the following doesn't work, though it should be more idiomatic:

// compute 'current' slice in term of the previous 'last' slice
let scan  = |last: ArrayViewMutD<T>, mut current: ArrayViewMutD<T>| {
    Zip::from(last).and(&mut current)    
        .for_each(|l, c| {
            *c = /* some expression in term of `l` */;
        });
    current
};
// iterate slices
array.axis_iter_mut(Axis(i)).reduce(scan);    

compiler output is

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/lib.rs:85:10
   |
85 |             array.axis_iter_mut(Axis(i)).reduce(scan);
   |                   ^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime defined on the function body at 66:15...
  --> src/lib.rs:66:15
   |
66 |             mut array: ArrayViewMutD<T>,
   |                        ^^^^^^^^^^^^^^^^
note: ...so that the type `ArrayBase<ViewRepr<&mut T>, ndarray::Dim<IxDynImpl>>` is not borrowed for too long
  --> src/lib.rs:85:4
   |
85 |             array.axis_iter_mut(Axis(i)).reduce(scan);
   |             ^^^^^
note: but, the lifetime must be valid for the anonymous lifetime #2 defined on the body at 76:16...
  --> src/lib.rs:76:16
   |
76 |               let scan  = |last: ArrayViewMutD<T>, mut current: ArrayViewMutD<T>| {
   |  _________________________^
77 | |                 let incr = ramp[i];
78 | |                 Zip::from(last).and(&mut current)
79 | |     //                 .into_par_iter()
...  |
83 | |                 current
84 | |             };
   | |_____________^
note: ...so that the types are compatible
  --> src/lib.rs:85:33
   |
85 |             array.axis_iter_mut(Axis(i)).reduce(scan);
   |                                          ^^^^^^
   = note: expected `ArrayBase<ViewRepr<&mut T>, _>`
              found `ArrayBase<ViewRepr<&mut T>, _>`

I suspect this comes from the fact axis_iter doesn't provide elements with lifetime 'a (same as the original array) but instead provide an anonymous lifetime '_ ? Not a big issue in my program since I found the first code sample to avoid my issue, but I'm curious :)

Thanks in advance

jturner314 commented 3 years ago

That's interesting. It works using a function with explicit lifetime annotations instead of a closure:

    fn scan<'a, T>(last: ArrayViewMutD<'a, T>, mut current: ArrayViewMutD<'a, T>) -> ArrayViewMutD<'a, T> {
        Zip::from(last).and(&mut current).for_each(|l, c| {
            *c = // do stuff
        });
        current
    };
    array.axis_iter_mut(Axis(i)).reduce(scan);

At first glance, I'm not sure how to achieve the same thing with a closure, since, AFAIK, there's no way to declare explicit lifetime annotations for a closure.

Anyway, for this application, I'd suggest the accumulate_axis_inplace method.