1

I want to create an Iterator capable of exposing neighbouring items as well. As long as I don't want to also change these items, it is fine and easy. But how to make a mutable variant of the same structure?

Immutable:

struct NearestNeighbours2D<'a, T> {
    mid: &'a T,
    left: &'a T,
    right: &'a T,
    top: &'a T,
    bot: &'a T,
}

struct EnumerateNearestNeighbours2D<'a, I>
where I: std::ops::Index<usize> {
    x: usize,
    y: usize,
    width: usize,
    height: usize,
    inner: &'a I
}

impl<'a, I: std::ops::Index<usize>> Iterator for EnumerateNearestNeighbours2D<'a, I>
where <I as std::ops::Index<usize>>::Output: std::marker::Sized {
    
    type Item = NearestNeighbours2D<'a, I::Output>;
    fn next(&mut self) -> std::option::Option<<Self as std::iter::Iterator>::Item> {

        let (top, left, mid, right, bot) = (
            (self.y - 1) * self.width + self.x,
            self.y * self.width + self.x - 1,
            self.y * self.width + self.x,
            self.y * self.width + self.x + 1,
            (self.y + 1) * self.width + self.x,
        );

        Some(
            NearestNeighbours2D {
                mid: &self.inner[mid],
                left: &self.inner[left],
                right: &self.inner[right],
                top: &self.inner[top],
                bot: &self.inner[bot],
            }
        )
    }
}

Mutable variant that doesn't work due to lifetimes:

struct NearestNeighbours2DMut<'a, T> {
    mid: &'a mut T,
    left: &'a mut T,
    right: &'a mut T,
    top: &'a mut T,
    bot: &'a mut T,
}

struct EnumerateNearestNeighbours2DMut<'a, I>
where I: std::ops::IndexMut<usize> {
    x: usize,
    y: usize,
    width: usize,
    height: usize,
    inner: &'a mut I
}

impl<'a, I: std::ops::IndexMut<usize>> Iterator for EnumerateNearestNeighbours2DMut<'a, I>
where <I as std::ops::Index<usize>>::Output: std::marker::Sized {
    
    type Item = NearestNeighbours2DMut<'a, I::Output>;
    fn next(&mut self) -> std::option::Option<<Self as std::iter::Iterator>::Item> {

        let (top, left, mid, right, bot) = (
            (self.y - 1) * self.width + self.x,
            self.y * self.width + self.x - 1,
            self.y * self.width + self.x,
            self.y * self.width + self.x + 1,
            (self.y + 1) * self.width + self.x,
        );

        Some(
            NearestNeighbours2DMut {
                mid: &mut self.inner[mid],
                left: &mut self.inner[left],
                right: &mut self.inner[right],
                top: &mut self.inner[top],
                bot: &mut self.inner[bot],
            }
        )
    }
}

Compiler points that:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
   --> src\lib.rs:99:27
    |
99  |                 mid: &mut self.inner[mid],
    |                           ^^^^^^^^^^^^^^^
    |
Latawiec
  • 341
  • 2
  • 10
  • @trentcl could you please take a look at that one as well? – Latawiec Aug 23 '20 at 21:55
  • 1
    FYI, people don't get notifications for being @ed in comments unless they are the author of the post being commented on or have commented previously on the same post. But I happen to be here anyway so I will take a look :-) – trent Aug 23 '20 at 23:23

1 Answers1

1

Unfortunately, there is no way EnumerateNearestNeighbors2DMut can be made correctly -- it is unsound. Each time you call next you get &mut references that potentially overlap with &mut references that were returned from the previous call to next. This means, if it worked, it would be violating the rules of references by creating aliased &muts.

This is the same reason that there is a std::slice::Windows, but no WindowsMut (although ChunksMut is fine because the chunks are non-overlapping).

Some problems (here's one example) give similar-looking error messages, but are actually solvable (in some cases with unsafe) because the items being referenced are in fact non-overlapping. Those solutions don't work here. If you could write an iterator that returns references to itself (a "streaming iterator"), the API could be made sound. However, Iterator doesn't allow this.

Here are three possible options for you. There are certainly others.

  • Don't allow mutable iteration (with neighbors) at all. Just expose iteration via shared (&) reference, and if you need to mutate the original grid, instead create a modified copy and swap it with the original after you're done iterating. This is frequently what you want anyway, if you're writing something like an image filter or a cellular automaton where each output depends on multiple inputs.

  • Accept a closure and use internal iteration in your API instead of external iteration. So instead of something like this:

    for neighbors in array.enumerate_2d_neighbors_mut() {
        println!("{}", neighbors.top);
    }
    

    you would write something like this:

    array.foreach_2d_neighbors_mut(|neighbors| {
        println!("{}", neighbors.top);
    });
    

    In this case the references to array's items are taken in a for loop inside the foreach_2d_neighbors_mut method and do not escape it. This API can be written pretty easily, even without unsafe code.

  • Use interior mutability (Cell, RefCell, Atomic???, etc.) to mutate through a & reference instead of needing &mut. Depending on what you're doing this might be the right way to go. Be aware that you can sneakily use Cell for interior mutability without having to change the type I, when I is a slice or vector. However this would not be my first choice most of the time.

trent
  • 25,033
  • 7
  • 51
  • 90
  • I googled `Cell` and first thing it points is exactly the mistake I made: "[...] it is only possible to have one of the following: Having several immutable references (&T) to the object (also known as aliasing). Having one mutable reference (&mut T) to the object (also known as mutability).". I only need it for reading to "blend" neighbouring elements and save in different buffer, but I was wondering "what if" in case I needed it in the future :D – Latawiec Aug 24 '20 at 08:33