es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

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.

Tags:  , , ,

Answer

  1. Avatar for davy.ai

    Explicación:

    El problema en el código original es que la función find_unused toma posesión de la Vec que se le pasa y luego intenta devolver una referencia a un elemento en la misma Vec. Esta referencia sería inválida, porque la Vec ha sido movida y los índices se han desplazado. Incluso si la Vec 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 usar enumerate() para obtener tanto el índice como el elemento, y luego modificar el elemento en su lugar:

    fn find_unused<'a>(data: &'a mut Vec<association>) -> Option<&'a String> {
        for k in 0.. {
            for (_, a) in data.iter_mut().enumerate() {
                if a.key == k && !a.used {
                    a.used = true;
                    return Some(&a.value);
                }
            }
        }
        None
    }
    

    Aquí, usamos iter_mut() para iterar sobre referencias mutables a los elementos de la Vec y enumerate() para obtener pares (índice, elemento). Ignoramos el índice usando _ y modificamos el campo value de la estructura Association en su lugar. Finalmente, devolvemos una referencia al campo value 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.

Comments are closed.