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.
El mensaje de error
Sección titulada «El mensaje de error»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 here4 | v.push(4); // ❌ cambia el Vec mientras `first` sigue vivo | ^^^^^^^^^ mutable borrow occurs here5 | println!("First: {first}"); // ...porque `first` se usa aquí | ----- immutable borrow later used hereEn 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í}Qué significa
Sección titulada «Qué significa»El compilador señala tres líneas, y necesitas las tres para entender el error:
- Línea 3:
&v[0]crea una referencia de solo lectura dentro dev. Ese es el préstamo inmutable (immutable borrow). - Línea 4:
v.push(4)necesita cambiarv, así que pide un préstamo mutable (mutable borrow). - Línea 5:
firstse 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.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»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.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»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: 1Now: [1, 2, 3, 4]Solución 2: Copia el valor
Sección titulada «Solución 2: Copia el valor»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: desplegarCola: ["desplegar", "probar", "publicar"]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]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]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.
Solución 5: Usa índices
Sección titulada «Solución 5: Usa índices»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]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.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»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.
Errores relacionados
Sección titulada «Errores relacionados»- 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 que aprendiste
Sección titulada «Comprueba lo que aprendiste»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
Para leer más
Sección titulada «Para leer más»- E0502 — Rust error codes index (en inglés): la explicación oficial de este error.
- Storing Lists of Values with Vectors — El libro de Rust (en inglés): incluye este mismo ejemplo de
let first = &v[0]; v.push(..)y explica por qué falla. También existe una traducción al español de la comunidad del libro. - References and Borrowing — El libro de Rust (en inglés): la regla de “muchos lectores o un solo escritor” al completo.
- Non-lexical lifetimes — The Edition Guide (en inglés): por qué ahora un préstamo termina en su último uso y no en la llave de cierre.
Vec— documentación de la biblioteca estándar (en inglés):retain,iter_mut,extendy los demás métodos que te permiten cambiar un vector de forma segura.