Cómo acceder a iterables mutables dentro de un bucle anidado
De vez en cuando me encuentro con el mismo problema de tomar prestadas (o no tomar prestadas) variables mutables dentro de un bucle, y finalmente me senté y compilé un ejemplo mínimo. Como resultado, el código es un poco tonto, pero es la versión más corta que se me ocurrió que destaca el problema:
struct Association {
used: bool,
key: usize,
value: String,
}
impl Association {
fn new(key: usize, value: &str) -> Self {
Association{used: false, key: key, value: value.to_string()}
}
}
fn find_unused<'a>(data: &'a mut Vec<association>) -> Option<&'a String> {
for k in 0.. {
for a in data {
if a.key == k && !a.used {
a.used = true;
return Some(&a.value);
}
}
}
None
}
fn main() {
let mut assoc = vec![
Association::new(7, "Hello"),
Association::new(9, "World")
];
println!("{}", find_unused(&mut assoc).unwrap());
println!("{}", find_unused(&mut assoc).unwrap());
}
Esto fallará con un error porque data
fue movido anteriormente. Si lo tomo prestado en su lugar, fallará porque fue prestado anteriormente. Me gustaría entender exactamente qué está sucediendo y cómo resolverlo en general. En particular, no quiero cambiar la estructura del código, incluso si es tonto. No quiero implementar una solución alternativa, porque esto es solo un ejemplo mínimo: Por favor, asuma que la anidación de los bucles es la forma “correcta” de hacerlo, incluso si aquí es completamente tonto, que definitivamente lo es.
Solo me gustaría saber cómo comunicarle al verificador de préstamos que lo que está sucediendo aquí es en realidad aceptable. Conozco una forma de hacerlo:
fn find_unused<'a>(data: &'a mut Vec<association>) -> Option<&'a String> {
for k in 0.. {
for j in 0..data.len() {
if data[j].key == k && !data[j].used {
data[j].used = true;
return Some(&data[j].value);
}
}
}
None
}
Esto compila sin errores y funciona como se pretende. En mi comprensión ingenua, debería haber una forma de expresar lo anterior con iteradores en lugar de índices, y me gustaría saber cómo se haría eso.
davy.ai
Explicación:
El problema en el código original es que la función
find_unused
toma posesión de laVec
que se le pasa y luego intenta devolver una referencia a un elemento en la mismaVec
. Esta referencia sería inválida, porque laVec
ha sido movida y los índices se han desplazado. Incluso si laVec
es mutable y los elementos se modifican en su lugar, el verificador de préstamos no puede verificar que las duraciones de las referencias sean válidas.Solución:
La solución a este problema es devolver una copia separada del valor de
String
o usar acceso indexado como se muestra en el código modificado. Sin embargo, es posible usar iteradores en lugar de acceso indexado para lograr el mismo resultado. El truco consiste en usarenumerate()
para obtener tanto el índice como el elemento, y luego modificar el elemento en su lugar:Aquí, usamos
iter_mut()
para iterar sobre referencias mutables a los elementos de laVec
yenumerate()
para obtener pares(índice, elemento)
. Ignoramos el índice usando_
y modificamos el campovalue
de la estructuraAssociation
en su lugar. Finalmente, devolvemos una referencia al campovalue
del elemento utilizado, al igual que en el código original. Este código se compila sin errores y produce la salida esperada para el caso de prueba dado.