rust borrow-checker

rust - ¿Devolver una referencia de un HashMap o Vec hace que un préstamo dure más allá del alcance en el que se encuentra?



borrow-checker (1)

Este es un problema conocido que se resolverá mediante vidas no léxicas , que se basa en MIR . Si sucede que está insertando en la misma clave que está buscando, le recomiendo que use la API de entrada en su lugar.

Puede agregar una pizca de ineficiencia para evitar esto por ahora.

HashMap

La idea general es agregar un booleano que le indique si un valor estaba presente o no. Este booleano no se aferra a una referencia, por lo que no hay préstamo:

use std::collections::BTreeMap; fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> { if map.contains_key(&key) { return map.get(&key); } map.insert(0, 0); None } fn main() { let mut map = BTreeMap::new(); do_stuff(&mut map, 42); println!("{:?}", map) }

Vec

Se pueden resolver casos similares utilizando el índice del elemento en lugar de la referencia. Al igual que en el caso anterior, esto puede introducir un poco de ineficiencia debido a la necesidad de verificar los límites de corte nuevamente.

En vez de

fn find_or_create_five<''a>(container: &''a mut Vec<u8>) -> &''a mut u8 { match container.iter_mut().find(|e| **e == 5) { Some(element) => element, None => { container.push(5); container.last_mut().unwrap() } } }

Puedes escribir:

fn find_or_create_five<''a>(container: &''a mut Vec<u8>) -> &''a mut u8 { let idx = container.iter().position(|&e| e == 5).unwrap_or_else(|| { container.push(5); container.len() - 1 }); &mut container[idx] }

Tiempos de vida no léxicos

Estos tipos de ejemplos son uno de los casos principales en el NLL RFC : Caso de problema # 3: flujo de control condicional a través de funciones .

Desafortunadamente, este caso específico no está listo a partir de Rust 1.34. Si opta por la función experimental -Zpolonius todas las noches, cada uno de estos ejemplos originales se compilará tal cual:

use std::collections::BTreeMap; fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> { if let Some(key) = map.get(&key) { return Some(key); } map.insert(0, 0); None }

fn find_or_create_five(container: &mut Vec<u8>) -> &mut u8 { match container.iter_mut().find(|e| **e == 5) { Some(element) => element, None => { container.push(5); container.last_mut().unwrap() } } }

Ver también:

  • ¿Cómo actualizar-o-insertar en un Vec?

    Este es el mismo problema sin devolver la referencia, que funciona con la implementación de NLL disponible en Rust 1.32.

  • El error de préstamo doble mutable en un bucle ocurre incluso con NLL activado

    Este problema pero en un caso un poco más complicado.

  • ¿Cuándo es necesario eludir el verificador de préstamos de Rust?

    La última escotilla de escape.

Tengo un error de compilación persistente en el que Rust se queja de que tengo un préstamo inmutable mientras trato de pedir prestado de forma mutable, pero el préstamo inmutable es de otro ámbito y no estoy aportando nada.

Tengo un código que verifica un valor en un mapa y, si está presente, lo devuelve, de lo contrario, debe mutar el mapa de varias maneras. El problema es que parece que no puedo encontrar una manera de hacer que Rust me permita hacer ambas cosas, a pesar de que las dos operaciones están totalmente separadas.

Aquí hay un código sin sentido que sigue la misma estructura que mi código y muestra el problema:

use std::collections::BTreeMap; fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> { // extra scope in vain attempt to contain the borrow { // borrow immutably if let Some(key) = map.get(&key) { return Some(key); } } // now I''m DONE with the immutable borrow, but rustc still thinks it''s borrowed map.insert(0, 0); // borrow mutably, which errors None }

Esto se equivoca con:

error[E0502]: cannot borrow `*map` as mutable because it is also borrowed as immutable --> src/lib.rs:14:5 | 3 | fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> { | - let''s call the lifetime of this reference `''1` ... 7 | if let Some(key) = map.get(&key) { | --- immutable borrow occurs here 8 | return Some(key); | --------- returning this value requires that `*map` is borrowed for `''1` ... 14 | map.insert(0, 0); // borrow mutably, which errors | ^^^^^^^^^^^^^^^^ mutable borrow occurs here

Esto no tiene ningún sentido para mí. ¿Cómo el préstamo inmutable sobrevive ese alcance? Una rama de esa match sale de la función mediante return , y la otra no hace nada y deja el alcance.

He visto que esto sucedió antes, donde estaba contrabando por error el préstamo fuera del alcance en alguna otra variable, ¡pero ese no es el caso aquí!

Es cierto que el préstamo está escapando del alcance a través de la declaración de return , pero es ridículo que eso bloquee los préstamos más abajo en la función: ¡el programa no puede regresar Y seguir adelante! Si devuelvo algo más allí, el error desaparece, así que creo que esto es lo que el verificador de préstamos está colgando. Esto se siente como un error para mí.

Desafortunadamente, no he podido encontrar ninguna manera de reescribir esto sin dar con el mismo error, por lo que es un error particularmente desagradable si ese es el caso.