Ir al contenido

Error E0502 de Rust: cannot borrow as mutable because it is also borrowed as immutable

E0502 significa que intentaste cambiar un valor mientras otra parte de tu código todavía lo estaba leyendo a través de una referencia. Aparece muchísimo con vectores, y la solución suele ser mover una línea o copiar un valor. Esta página te da primero la solución y después explica por qué Rust insiste en ella. Para los demás errores de esta serie, mira Errores del compilador de Rust.

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let first = &v[0]; // una referencia dentro del Vec
| - immutable borrow occurs here
4 | v.push(4); // ❌ cambia el Vec mientras `first` sigue vivo
| ^^^^^^^^^ mutable borrow occurs here
5 | println!("First: {first}"); // ...porque `first` se usa aquí
| ----- immutable borrow later used here

En español: “no se puede tomar prestado v como mutable porque también está prestado como inmutable”. Las etiquetas marcan dónde empieza el préstamo inmutable, dónde se pide el mutable y dónde se vuelve a usar el primero.

Este es el programa que lo produce:

fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0]; // una referencia dentro del Vec
v.push(4); // ❌ cambia el Vec mientras `first` sigue vivo
println!("First: {first}"); // ...porque `first` se usa aquí
}

El compilador señala tres líneas, y necesitas las tres para entender el error:

  1. Línea 3: &v[0] crea una referencia de solo lectura dentro de v. Ese es el préstamo inmutable (immutable borrow).
  2. Línea 4: v.push(4) necesita cambiar v, así que pide un préstamo mutable (mutable borrow).
  3. Línea 5: first se usa otra vez. Esta es la línea que mantiene vivo el primer préstamo durante la línea 4.

Sin la línea 5, el código compilaría. El problema no es que existan los dos préstamos, sino que se solapan.

La regla del borrowing (préstamo) es: muchos lectores o un solo escritor, nunca las dos cosas a la vez. La viste en la página de Borrowing.

Con un Vec hay una razón muy concreta. Un Vec guarda sus elementos en un único bloque de memoria. Cuando haces push y ese bloque está lleno, el Vec mueve todo a un bloque nuevo y más grande, y libera el antiguo. Cualquier referencia al bloque antiguo (como first) apunta ahora a memoria liberada. En C++ este es el famoso bug de “iterator invalidation” (invalidación de iteradores), y puede hacer que tu programa falle o que lea basura sin avisar.

Es como apuntar la dirección de un amigo justo antes de que se mude a una casa más grande. Tu nota parece correcta, pero apunta a un edificio vacío. Rust se niega a compilar el código en lugar de dejarte ir al edificio vacío.

Solución 1: Termina de leer antes de escribir

Sección titulada «Solución 1: Termina de leer antes de escribir»

Elige esta primero. Desde Rust 2018, un préstamo termina en su último uso, no en la llave de cierre. Esto se llama non-lexical lifetimes. Así que basta con terminar de usar la referencia antes de cambiar el vector:

fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0];
println!("First: {first}"); // último uso de `first`: el préstamo termina aquí
v.push(4); // ✅ ya nadie está leyendo
println!("Now: {:?}", v);
}

Salida:

First: 1
Now: [1, 2, 3, 4]
▶ Pruébalo tú mismo

Elige esta cuando necesitas el valor antiguo después del cambio. Toma una copia en lugar de una referencia. Con números, basta con quitar el &. Con un String, usa .clone():

fn main() {
let mut queue = vec![String::from("desplegar"), String::from("probar")];
let next = queue[0].clone(); // nuestra propia copia, no una referencia
queue.push(String::from("publicar"));
println!("Siguiente: {next}");
println!("Cola: {:?}", queue);
}

Salida:

Siguiente: desplegar
Cola: ["desplegar", "probar", "publicar"]
▶ Pruébalo tú mismo

Con let first = v[0]; en un Vec<i32> obtienes una copia gratis, porque i32 es Copy.

Solución 3: Usa iter_mut o retain para cambiar los elementos en el sitio

Sección titulada «Solución 3: Usa iter_mut o retain para cambiar los elementos en el sitio»

Elige esta cuando escribiste un bucle como for p in prices.iter() y luego intentaste hacer prices.remove(i) o cambiar elementos dentro de él. Eso también da E0502. La biblioteca estándar tiene métodos hechos justo para esto:

fn main() {
let mut prices = vec![120, 45, 300, 80];
// cambia cada elemento en el sitio
for p in prices.iter_mut() {
*p = *p * 9 / 10; // 10 % de descuento
}
// quita los elementos que no quieres
prices.retain(|&p| p >= 50);
println!("{:?}", prices);
}

Salida:

[108, 270, 72]
▶ Pruébalo tú mismo

iter_mut te da un &mut por elemento, así que nunca hay más de un escritor. retain hace el borrado por ti, de forma segura y en una sola pasada. Mira Vectores e Iteradores.

Solución 4: Reúne los cambios y aplícalos después

Sección titulada «Solución 4: Reúne los cambios y aplícalos después»

Elige esta cuando necesitas añadir elementos según lo que lees. Primero lee y reúne, y luego escribe en un paso aparte:

fn main() {
let mut scores = vec![10, 25, 7, 40];
let bonuses: Vec<i32> = scores.iter().filter(|&&s| s > 20).map(|s| s * 2).collect();
scores.extend(bonuses); // el préstamo de lectura terminó, así que podemos escribir
println!("{:?}", scores);
}

Salida:

[10, 25, 7, 40, 50, 80]
▶ Pruébalo tú mismo

El Vec extra cuesta un poco de memoria, pero el código queda claro y es evidente que es correcto. Normalmente es un buen trato.

Elige esta como último recurso, cuando las demás soluciones no encajan. Un índice es solo un número, no un préstamo, así que no bloquea los cambios:

fn main() {
let mut scores = vec![10, 25, 7];
for i in 0..scores.len() {
if scores[i] > 20 {
scores.push(scores[i] * 2);
}
}
println!("{:?}", scores);
}

Salida:

[10, 25, 7, 50]
▶ Pruébalo tú mismo

0..scores.len() se calcula una sola vez, antes de que empiece el bucle, así que los elementos nuevos no se recorren. Ten cuidado cuando quites elementos por índice: las posiciones se desplazan y puedes saltarte elementos o provocar un panic. Para quitar, retain (Solución 3) es más seguro.

Hacer push en un Vec mientras lo recorres. for s in &scores { scores.push(..) } toma prestado scores durante todo el bucle. Solución: reúne primero y haz extend después (Solución 4), o recorre por índice (Solución 5).

Llamar a un método &mut self mientras recorres un campo. Es muy común dentro de los bloques impl:

fn audit(&mut self) {
for item in &self.items {
self.record(item); // ❌ cannot borrow `*self` as mutable because it is also borrowed as immutable
}
}

self.record toma prestado mutablemente todo self, incluido self.items. Solución: toca el otro campo directamente (self.log.push(item.clone())), porque Rust permite tomar prestados dos campos distintos a la vez. O clona primero los elementos. Mira Structs.

Guardar un valor de un HashMap mientras insertas. let apples = stock.get("apples").unwrap();, luego stock.insert(..) y después usar apples da E0502. Solución: copia el valor con let apples = *stock.get("apples").unwrap(); o con .copied(). Mira HashMap.

  • E0499: dos préstamos mutables a la vez. La misma idea, con dos escritores en lugar de un lector y un escritor.
  • E0382: moviste un valor y luego lo usaste. Suele aparecer cuando intentas arreglar E0502 moviendo en lugar de tomar prestado.
  • E0106: falta un lifetime en una función que devuelve una referencia.
  • Todos los errores del compilador: la lista completa de páginas de errores.

Comprueba lo aprendido

1. ¿Por qué no compila let first = &v[0]; v.push(4); println!("{first}");?

2. Quieres quitar todos los elementos menores que 50 de un Vec<i32>. ¿Cuál es la solución más limpia para E0502?

3. ¿Cuándo termina un préstamo inmutable en el Rust actual?

Puntuación: 0 / 3