¿Por qué el comprobador de préstamos genera un error cuando mantengo una referencia a un elemento en un vector que se agrega dentro de un bucle?
Tengo dos estructuras, Holder
y Held
. Holder
contiene una referencia a Held
. Held
contiene un i32
:
struct Holder<'a> {
val: &'a Held,
}
<h1>[derive(Debug)]</h1>
struct Held(i32);
<p>Quiero crear 10 <code>Holder</code>s en un <code>Vec<></code> llamado <code>holders</code>. Como <code>Holder</code> toma una referencia a la estructura <code>Held</code>, también crearé un <code>Vec<></code> llamado <code>heldvals</code> que almacenará las estructuras <code>Held</code> para el alcance de la función <code>main</code>:</p>
<p>“`rust
pub fn main() {
// contiene los Holder
s
let mut holders = vec![];
// contiene las estructuras
Held
let mut heldvals = vec![]; for i in 0..10 { heldvals.push(Held(i));holders.push(Holder { val: &heldvals.last().unwrap(), });
}
}
Cuando intento compilar este programa, obtengo un error:<code>none
error[E0502]: cannot borrow `heldvals` as mutable because it is also borrowed as immutable
|
| heldvals.push(Held(i));
| ^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
|
| holders.push(Holder {
| ------- immutable borrow later used here
| val: &heldvals.last().unwrap(),
| -------- immutable borrow occurs here
</code>Como solución alternativa, decidí utilizar <code>unsafe</code>, lo cual funciona sin errores. Incluso implementé el rasgo <code>Drop</code> para confirmar que no hay problemas de memoria.
```rust
// ...
impl Drop for Held {
fn drop(&mut self) {
dbg!(self);
}
}pub fn main() {
let mut holders = vec![];<pre><code>let mut heldvals = vec![];
let hptr = &mut heldvals as *mut Vec<Held>;for i in 0..10 {
println!("creation");
unsafe {
(*hptr).push(Held(i));
}holders.push(Holder {
val: &heldvals.last().unwrap(),
});
println!("replacement");
}
</code></pre>}
</code></pre><p>Ejecutar el código anterior proporciona la siguiente salida (reducida):</p>
<p><code>none
creation
replacement (10 veces)
[src/main.rs:12] self = Held(
0,
)
...
[src/main.rs:12] self = Held(
9,
)
</code></p><p>Valgrind tampoco muestra fugas de memoria ni problemas:</p>
<p>```none
HEAP SUMMARY:
in use at exit: 0 bytes in 0 blocks
total heap usage: 18 allocs, 18 frees, 3,521 bytes allocatedAll heap blocks were freed -- no leaks are possible
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
```¿Hay alguna manera de evitar el uso de
unsafe
? DescubríVec::reserve()
, ¿es una buena idea?https://stackoverflow.com/q/47618823/155423 y otras respuestas similares son demasiado simplistas y no explican la relación entre los bucles y los errores de préstamo. Además, no dan una indicación de una solución alternativa.
La utilización de contadores de referencia es imposible para mí. Quiero una manera simple de mantener las referencias hasta que el programa finalice.
davy.ai
El problema con la implementación inicial es que cuando se llama a
heldvals.push(Held(i));
dentro del bucle, se crea un préstamo mutable deheldvals
. Este préstamo dura hasta el final de la iteración del bucle. En la siguiente iteración, el préstamo todavía está activo, y un intento de crear un préstamo inmutable deheldvals
a través deheldvals.last()
causa un error en tiempo de compilación debido a que el verificador de préstamos previene múltiples préstamos.Una forma de evitar el uso de
unsafe
sería crear el vectorheldvals
fuera del bucle y preasignar espacio para él usandoVec::with_capacity()
. Esto reservará suficiente memoria para que el vector contenga todos los valores que se insertarán en él, sin necesidad de cambiar de tamaño. Luego, los valores se pueden insertar en el espacio preasignado, y se puede construirholders
utilizando préstamos inmutables de los elementos del vector.Aquí hay una implementación de ejemplo que evita el uso de
unsafe
:Usando
Vec::with_capacity()
se asegura de que el vector tiene suficiente espacio para los 10 valores, y usando un bucle para insertar los elementos y construir losHolder
s asegura que no haya préstamos mutables que se superpongan con préstamos inmutables.Usar
Vec::reserve()
en la implementación inicial también es un enfoque válido, pero puede ser más complicado obtener la cantidad de espacio necesaria exactamente correcta.En general, se recomienda evitar el uso de
unsafe
cuando sea posible y confiar en el verificador de préstamos para evitar problemas de memoria.