Referencia mutable en objeto devuelto por Iterator
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
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 unforbucle dentro delforeach_2d_neighbors_mutmétodo y no se escapan de él. Esta API se puede escribir con bastante facilidad, incluso sinunsafecó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.