¿Cómo puedo crear mi propia estructura de datos con un iterador que devuelve referencias mutables?
He creado una estructura de datos en Rust y quiero crear iteradores para ella. Los iteradores inmutables son bastante fáciles. Actualmente tengo esto y funciona bien:
// This is a mock of the "real" EdgeIndexes class as
// the one in my real program is somewhat complex, but
// of identical type
struct EdgeIndexes;
impl Iterator for EdgeIndexes {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
Some(0)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(0, None)
}
}
pub struct CGraph<E> {
nodes: usize,
edges: Vec<E>,
}
pub struct Edges<'a, E: 'a> {
index: EdgeIndexes,
graph: &'a CGraph<E>,
}
impl<'a, E> Iterator for Edges<'a, E> {
type Item = &'a E;
fn next(&mut self) -> Option<Self::Item> {
match self.index.next() {
None => None,
Some(x) => Some(&self.graph.edges[x]),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.index.size_hint()
}
}
Quiero crear un iterador que también devuelva referencias mutables. Intenté hacer esto, pero no puedo encontrar una manera de compilarlo:
pub struct MutEdges<'a, E: 'a> {
index: EdgeIndexes,
graph: &'a mut CGraph<E>,
}
impl<'a, E> Iterator for MutEdges<'a, E> {
type Item = &'a mut E;
fn next(&mut self) -> Option<&'a mut E> {
match self.index.next() {
None => None,
Some(x) => self.graph.edges.get_mut(x),
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.index.size_hint()
}
}
Al compilar esto se produce el siguiente error:
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
--> src/lib.rs:54:24
|
54 | Some(x) => self.graph.edges.get_mut(x),
| ^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 51:5...
--> src/lib.rs:51:5
|
51 | / fn next(&mut self) -> Option<&'a mut E> {
52 | | match self.index.next() {
53 | | None => None,
54 | | Some(x) => self.graph.edges.get_mut(x),
55 | | }
56 | | }
| |_____^
note: ...so that reference does not outlive borrowed content
--> src/lib.rs:54:24
|
54 | Some(x) => self.graph.edges.get_mut(x),
| ^^^^^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 48:6...
--> src/lib.rs:48:6
|
48 | impl<'a, E> Iterator for MutEdges<'a, E> {
| ^^
= note: ...so that the expression is assignable:
expected std::option::Option<&'a mut E>
found std::option::Option<&mut E>
No estoy seguro de cómo interpretar estos errores y cómo cambiar mi código para permitir MutEdges
devolver referencias mutables.
Enlace al parque infantil con código .
No puedes compilar esto porque las referencias mutables son más restrictivas que las inmutables. Una versión abreviada que ilustra el problema es esta:
struct MutIntRef<'a> {
r: &'a mut i32
}
impl<'a> MutIntRef<'a> {
fn mut_get(&mut self) -> &'a mut i32 {
&mut *self.r
}
}
fn main() {
let mut i = 42;
let mut mir = MutIntRef { r: &mut i };
let p = mir.mut_get();
let q = mir.mut_get();
println!("{}, {}", p, q);
}
Lo que produce el mismo error:
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
--> src/main.rs:7:9
|
7 | &mut *self.r
| ^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 6:5...
--> src/main.rs:6:5
|
6 | / fn mut_get(&mut self) -> &'a mut i32 {
7 | | &mut *self.r
8 | | }
| |_____^
note: ...so that reference does not outlive borrowed content
--> src/main.rs:7:9
|
7 | &mut *self.r
| ^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 5:6...
--> src/main.rs:5:6
|
5 | impl<'a> MutIntRef<'a> {
| ^^
note: ...so that reference does not outlive borrowed content
--> src/main.rs:7:9
|
7 | &mut *self.r
| ^^^^^^^^^^^^
Si echamos un vistazo a la función principal, obtenemos dos referencias mutables llamadas p
y q
ambas alias la ubicación de memoria de i
. Esto no esta permitido. En Rust, no podemos tener dos referencias mutables con alias y que ambas sean utilizables. La motivación de esta restricción es la observación de que la mutación y el alias no combinan bien con respecto a la seguridad de la memoria. Entonces, es bueno que el compilador haya rechazado el código. Si se compilara algo como esto, sería fácil obtener todo tipo de errores de corrupción de memoria.
La forma en que Rust evita este tipo de peligro es manteniendo utilizable como máximo una referencia mutable. Entonces, si desea crear una referencia mutable a X basada en una referencia mutable a Y donde X es propiedad de Y, será mejor que nos aseguremos de que mientras exista la referencia a X, ya no podamos tocar la otra referencia a Y. . En Rust esto se logra mediante vidas y préstamos. En tal caso, el compilador considera que la referencia original es tomada prestada y esto también tiene un efecto en el parámetro de vida útil de la referencia resultante. si cambiamos
fn mut_get(&mut self) -> &'a mut i32 {
&mut *self.r
}
a
fn mut_get(&mut self) -> &mut i32 { // <-- no 'a anymore
&mut *self.r // Ok!
}
el compilador deja de quejarse de esta get_mut
función. Ahora devuelve una referencia con un parámetro de duración que coincide &self
y 'a
ya no. Esto hace mut_get
una función con la que "tomas prestado" self
. Y es por eso que el compilador se queja de una ubicación diferente:
error[E0499]: cannot borrow `mir` as mutable more than once at a time
--> src/main.rs:15:13
|
14 | let p = mir.mut_get();
| --- first mutable borrow occurs here
15 | let q = mir.mut_get();
| ^^^ second mutable borrow occurs here
16 | println!("{}, {}", p, q);
| - first borrow later used here
Aparentemente, el compilador realmente consideró que mir
lo tomaran prestado. Esto es bueno. Esto significa que ahora sólo hay una manera de llegar a la ubicación de memoria de i
: p
.
Ahora quizás se pregunte: ¿Cómo lograron los autores de la biblioteca estándar escribir el iterador de vector mutable? La respuesta es simple: utilizaron código inseguro. No hay otra manera. El compilador de Rust simplemente no sabe que cada vez que le pide a un iterador de vector mutable el siguiente elemento, obtiene una referencia diferente cada vez y nunca la misma referencia dos veces. Por supuesto, sabemos que un iterador de este tipo no le dará la misma referencia dos veces y eso hace que sea seguro ofrecer este tipo de interfaz al que está acostumbrado. No necesitamos "congelar" dicho iterador. Si las referencias que devuelve un iterador no se superponen, es seguro no tener que pedir prestado el iterador para acceder a un elemento. Internamente, esto se hace utilizando código no seguro (convirtiendo punteros sin formato en referencias).
La solución fácil para su problema podría ser confiar en MutItems
. Este ya es un iterador mutable proporcionado por la biblioteca sobre un vector. Por lo tanto, puede salirse con la suya usando eso en lugar de su propio tipo personalizado, o puede incluirlo dentro de su tipo de iterador personalizado. En caso de que no pueda hacerlo por algún motivo, deberá escribir su propio código inseguro. Y si lo haces, asegúrate de que
- No creas múltiples referencias mutables con ese alias. Si lo hiciera, esto violaría las reglas de Rust e invocaría un comportamiento indefinido.
- No olvide usar el
PhantomData
tipo para decirle al compilador que su iterador es un tipo similar a una referencia donde no se permite reemplazar la vida útil por una más larga y, de lo contrario, podría crear un iterador colgante.