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.

¿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 Holders
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: &amp;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: &amp;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 allocated

All 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.

Tags:  , ,

Answer

  1. Avatar for 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 de heldvals. 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 de heldvals a través de heldvals.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 vector heldvals fuera del bucle y preasignar espacio para él usando Vec::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 construir holders utilizando préstamos inmutables de los elementos del vector.

    Aquí hay una implementación de ejemplo que evita el uso de unsafe:

    pub fn main() {
        // contiene los `Held`s
        let mut heldvals = Vec::with_capacity(10);
    
        for i in 0..10 {
            heldvals.push(Held(i));
        }
    
        // contiene los `Holder`s
        let mut holders = vec![];
    
        for held_val in &heldvals {
            holders.push(Holder {
                val: held_val,
            });
        }
    }
    

    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 los Holders 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.

Comments are closed.