Rust: cannot borrow as mutable because it is also borrowed as immutable
Cómo entender y resolver los errores del borrow checker de Rust. Guía práctica para E0502, E0505, E0506 y los patrones para superarlos.
¿Por qué ocurre?
El borrow checker de Rust garantiza memory safety en tiempo de compilación. El error ocurre porque las reglas de borrowing prohíben: - Tener una referencia mutable (`&mut`) mientras existen referencias inmutables (`&`) al mismo dato - Tener más de una referencia mutable al mismo dato simultáneamente - Usar un valor después de haberlo movido (moved value) - Devolver referencias a datos locales que se liberan al salir de la función
Solución paso a paso
1. El error clásico: mezclar & y &mut
fn main() {
let mut v = vec![1, 2, 3]; let primer = &v[0]; // ← borrow inmutable
v.push(4); // ❌ borrow mutable mientras existe el inmutable
println!("{}", primer);
// ✅ Solución 1: usar el borrow inmutable antes de la mutación
let primer = v[0]; // copiar el valor (si implementa Copy)
v.push(4);
println!("{}", primer); // ✅
// ✅ Solución 2: limitar el scope del borrow inmutable
{
let primer = &v[0];
println!("{}", primer);
} // borrow inmutable termina aquí
v.push(4); // ✅
}
2. Múltiples referencias mutables — usar RefCell o Mutex
use std::cell::RefCell;// RefCell: mutabilidad interior (single-threaded)
let dato = RefCell::new(vec![1, 2, 3]);
let mut ref1 = dato.borrow_mut();
ref1.push(4);
drop(ref1); // liberar antes de otro borrow
let ref2 = dato.borrow();
println!("{:?}", *ref2); // ✅
// Mutex: para multi-threading
use std::sync::{Arc, Mutex};
let dato = Arc::new(Mutex::new(vec![1, 2, 3]));
let mut locked = dato.lock().unwrap();
locked.push(4);
3. Valor movido (use of moved value)
// ❌ s se mueve a la función y ya no está disponible
fn tomar_ownership(s: String) {
println!("{}", s);
}let s = String::from("hola");
tomar_ownership(s);
println!("{}", s); // ❌ error: value borrowed here after move
// ✅ Opción 1: pasar referencia
fn usar_ref(s: &str) {
println!("{}", s);
}
let s = String::from("hola");
usar_ref(&s);
println!("{}", s); // ✅
// ✅ Opción 2: clonar (tiene coste de memoria)
tomar_ownership(s.clone());
println!("{}", s); // ✅
4. Devolver referencias — lifetimes
// ❌ Devolver referencia a dato local
fn obtener_string() -> &str { // ❌ missing lifetime specifier
let s = String::from("hola");
&s // ← s se libera al salir de la función
}// ✅ Devolver el String directamente (ownership)
fn obtener_string() -> String {
String::from("hola")
}
// ✅ Lifetime explícito cuando devuelves referencia a un parámetro
fn mas_larga<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() { s1 } else { s2 }
}
5. Patrones idiomáticos para evitar conflictos
// ✅ Indexar y modificar: usar índice numérico
let mut v = vec![1, 2, 3];
let len = v.len(); // obtener antes del borrow mutable
for i in 0..len {
v[i] *= 2;
}// ✅ Usar entry API en HashMap para evitar double borrow
use std::collections::HashMap;
let mut mapa: HashMap = HashMap::new();
mapa.entry("clave".to_string())
.and_modify(|v| *v += 1)
.or_insert(1);
Cómo evitarlo en el futuro
- Lee los mensajes de error del compilador completos — Rust tiene los mejores mensajes de error de cualquier lenguaje - Usa `cargo clippy` para sugerencias de código idiomático - Aprende los patrones: `clone()` para simplificar, luego optimiza con referencias - `RefCell` para mutabilidad interior en single-thread; `Arc<Mutex<T>>` para multi-thread - El libro oficial "The Rust Programming Language" (gratis online) explica el borrow checker en detalle
¿Quieres que una IA te ayude? Genera el prompt perfecto para tu error:
Generador de Prompts