rust - No se puede obtener una referencia mutable al iterar una estructura recursiva: no se puede tomar prestada como mutable más de una vez a la vez
borrowing (4)
Estoy tratando de navegar una estructura de datos recursiva iterativamente para insertar elementos en una determinada posición. A mi entender limitado, esto significa tomar una referencia mutable a la raíz de la estructura y reemplazarla sucesivamente por una referencia a su seguidor:
type Link = Option<Box<Node>>;
struct Node {
next: Link
}
struct Recursive {
root: Link
}
impl Recursive {
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
while let Some(ref mut node) = *anchor {
anchor = &mut node.next;
}
anchor
}
}
Sin embargo, esto falla:
error[E0499]: cannot borrow `anchor.0` as mutable more than once at a time
--> src/main.rs:14:24
|
14 | while let Some(ref mut node) = *anchor {
| ^^^^^^^^^^^^
| |
| second mutable borrow occurs here
| first mutable borrow occurs here
...
18 | }
| - first borrow ends here
error[E0506]: cannot assign to `anchor` because it is borrowed
--> src/main.rs:15:13
|
14 | while let Some(ref mut node) = *anchor {
| ------------ borrow of `anchor` occurs here
15 | anchor = &mut node.next;
| ^^^^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `anchor` occurs here
error[E0499]: cannot borrow `*anchor` as mutable more than once at a time
--> src/main.rs:17:9
|
14 | while let Some(ref mut node) = *anchor {
| ------------ first mutable borrow occurs here
...
17 | anchor
| ^^^^^^ second mutable borrow occurs here
18 | }
| - first borrow ends here
Esto tiene sentido ya que tanto el
anchor
como el
node
refieren a la misma estructura, pero en realidad ya no me importa el
anchor
después de desestructurarla.
¿Cómo se podría implementar correctamente
back()
usando Safe Rust?
El código original funciona como está una vez que se habilitan las vidas no léxicas :
#![feature(nll)]
type Link = Option<Box<Node>>;
struct Node {
next: Link
}
struct Recursive {
root: Link
}
impl Recursive {
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
while let Some(node) = anchor {
anchor = &mut node.next;
}
anchor
}
}
fn main() {}
Los tiempos de vida no léxicos aumentan la precisión del verificador de préstamos del compilador, lo que le permite ver que el préstamo mutable del
anchor
ya no se usa.
También podemos simplificar las palabras clave en
if let
debe a cambios recientes en el idioma.
Es posible ... pero desearía tener una solución más elegante.
El truco NO es tomar prestado del
anchor
y, por lo tanto, hacer malabarismos entre dos acumuladores:
- uno con la referencia al nodo actual
- al otro se le asigna la referencia al siguiente nodo
Esto me lleva a:
impl Recursive {
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
loop {
let tmp = anchor;
if let Some(ref mut node) = *tmp {
anchor = &mut node.next;
} else {
anchor = tmp;
break;
}
}
anchor
}
}
No es exactamente bonito, pero esto es algo que el corrector de préstamos puede retrasar, así que ¯ / _ (ツ) _ / ¯.
@ker ha mejorado esto creando un temporal sin nombre:
impl Recursive {
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
loop {
match {anchor} {
&mut Some(ref mut node) => anchor = &mut node.next,
other => return other,
}
}
}
}
El truco aquí es que el uso de
{anchor}
mueve
el contenido del
anchor
a un temporal sin nombre en el que se ejecuta la coincidencia.
Por lo tanto, en el bloque de
match
no tomamos prestado del
anchor
sino del temporal, dejándonos libres para modificar el
anchor
.
Vea la publicación de blog relacionada
Cosas que hace la función de identidad (en Rust)
.
Funciona:
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
while anchor.is_some(){
anchor = &mut {anchor}.as_mut().unwrap().next;
}
anchor
}
Puede usar la recursividad para satisfacer al verificador de préstamos.
Esto tiene la desventaja de crear un marco de pila para cada elemento de su lista.
Si su lista es larga, esto definitivamente se encontrará con un desbordamiento de pila.
LLVM optimizará el método
Node::back
en un bucle (vea el IR LLVM generado en el
playground
)
impl Node {
fn back(&mut self) -> &mut Link {
match self.next {
Some(ref mut node) => node.back(),
None => &mut self.next,
}
}
}
impl Recursive {
fn back(&mut self) -> Option<&mut Link> {
self.root.as_mut().map(|node| node.back())
}
}