Referencia mutable en objeto devuelto por Iterator

Aug 23 2020

Quiero crear un Iteratorcapaz de exponer elementos vecinos también. Siempre que no quiera cambiar también estos elementos, está bien y es fácil. Pero, ¿cómo hacer una variante mutable de la misma estructura?

Inmutable:

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],
            }
        )
    }
}

Variante mutable que no funciona debido a la vida útil:

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],
            }
        )
    }
}

El compilador señala que:

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],
    |                           ^^^^^^^^^^^^^^^
    |

Respuestas

1 trentcl Aug 24 2020 at 00:26

Desafortunadamente, no hay forma de que EnumerateNearestNeighbors2DMutse pueda hacer correctamente, no es sólido. Cada vez que llama next, obtiene &mutreferencias que potencialmente se superponen con &mutreferencias devueltas de la llamada anterior a next. Esto significa que, si funcionara, estaría violando las reglas de referencias al crear &muts alias .

Esta es la misma razón por la que hay un std::slice::Windows, pero no WindowsMut(aunque ChunksMutestá bien porque los trozos no se superponen).

Algunos problemas ( aquí hay un ejemplo ) dan mensajes de error de apariencia similar, pero en realidad se pueden resolver (en algunos casos con unsafe) porque los elementos a los que se hace referencia no se superponen. Esas soluciones no funcionan aquí. Si pudiera escribir un iterador que devuelva referencias a sí mismo (un "iterador de transmisión"), la API podría ser sólida. Sin embargo, Iteratorno permite esto.

Aquí tienes tres opciones posibles. Ciertamente hay otros.

  • No permita la iteración mutable (con vecinos) en absoluto. Simplemente exponga la iteración a través de la &referencia shared ( ) y, si necesita mutar la cuadrícula original, cree una copia modificada e intercambiela con la original una vez que haya terminado de iterar. Esto es con frecuencia lo que desea de todos modos, si está escribiendo algo como un filtro de imagen o un autómata celular donde cada salida depende de múltiples entradas.

  • Acepte un cierre y use la iteración interna en su API en lugar de la iteración externa. Entonces, en lugar de algo como esto:

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

    escribirías algo como esto:

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

    En este caso, las referencias a arraylos elementos de 'se toman en un forbucle dentro del foreach_2d_neighbors_mutmétodo y no se escapan de él. Esta API se puede escribir con bastante facilidad, incluso sin unsafecódigo.

  • Utilice mutabilidad interior ( Cell, RefCell, Atomic???, etc.) para mutar a través de una &referencia en lugar de necesitar &mut. Dependiendo de lo que esté haciendo, este podría ser el camino correcto a seguir. Tenga en cuenta que puede usar furtivamente Cellpara la mutabilidad interior sin tener que cambiar el tipo I, cuando Ies un corte o un vector . Sin embargo, esta no sería mi primera opción la mayor parte del tiempo.