Error E0499 de Rust: cannot borrow as mutable more than once at a time
El error E0499 significa que intentaste tener dos referencias mutables (&mut) al mismo valor al mismo tiempo. Es uno de los errores más comunes del borrow checker (el comprobador de préstamos), y la solución suele ser pequeña: a menudo basta con mover una línea. (¿Buscas otro error? Mira el resumen de errores del compilador.)
El mensaje de error
Sección titulada «El mensaje de error»error[E0499]: cannot borrow `balance` as mutable more than once at a time --> src/main.rs:5:20 |4 | let deposit = &mut balance; | ------------ first mutable borrow occurs here5 | let withdraw = &mut balance; | ^^^^^^^^^^^^ second mutable borrow occurs here6 |7 | *deposit += 50; | -------------- first borrow later used hereEn español: “no se puede tomar prestado balance como mutable más de una vez a la vez”. Las etiquetas marcan el primer préstamo mutable, el segundo y el sitio donde el primero se vuelve a usar.
Este es el programa que lo produce:
fn main() { let mut balance = 100;
let deposit = &mut balance; let withdraw = &mut balance;
*deposit += 50; *withdraw -= 30;
println!("Balance: {balance}");}Qué significa
Sección titulada «Qué significa»El compilador señala tres sitios, y juntos cuentan toda la historia:
- Línea 4:
depositse convierte en un préstamo mutable debalance. - Línea 5:
withdrawintenta convertirse en un segundo préstamo mutable debalance. - Línea 7:
depositse sigue usando después de la línea 5. Así que el primer préstamo sigue vivo cuando empieza el segundo.
Esa tercera etiqueta es la importante. Dos préstamos &mut solo son un problema cuando sus lifetimes (tiempos de vida) se solapan. Si deposit hubiera terminado antes de la línea 5, no habría error.
Piensa en un documento compartido con una sola llave de “edición”. Quien tenga la llave puede cambiar el documento. Rust no hace una copia de la llave: la segunda persona tiene que esperar a que la primera la devuelva.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»La regla del borrowing (préstamo) en Rust es: en cada momento puedes tener muchos lectores (&) o un solo escritor (&mut), nunca más de un escritor. Una referencia &mut es una promesa: “mientras yo exista, nadie más puede tocar este valor”.
Esa promesa evita bugs reales. Si dos trozos de código pudieran cambiar el mismo valor a la vez, uno podría redimensionar un Vec mientras el otro sigue apuntando a su memoria antigua. En otros lenguajes, eso es un fallo o una corrupción silenciosa de datos. Entre hilos, es una carrera de datos (data race).
Gracias a esta regla, el compilador puede estar seguro de que un &mut es la única forma de llegar a los datos mientras exista. Obtienes esa seguridad sin ningún coste en tiempo de ejecución. El precio es que a veces tienes que dejarle más claro al compilador que tus dos préstamos no chocan de verdad.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»Solución 1: Termina el primer préstamo antes de empezar el segundo
Sección titulada «Solución 1: Termina el primer préstamo antes de empezar el segundo»Elige esta primero. Arregla la mayoría de los errores E0499. Un préstamo dura hasta su último uso, no hasta el final del bloque. Así que haz todo el trabajo con la primera referencia y después crea la segunda:
fn main() { let mut balance = 100;
let deposit = &mut balance; *deposit += 50; // último uso de `deposit`: su préstamo termina aquí
let withdraw = &mut balance; // ✅ ahora nadie más lo tiene prestado *withdraw -= 30;
println!("Balance: {balance}");}Salida:
Balance: 120Muchas veces ni siquiera necesitas las variables extra: balance += 50; y luego balance -= 30; funciona igual de bien.
Solución 2: Toma prestadas partes que no se solapan
Sección titulada «Solución 2: Toma prestadas partes que no se solapan»A veces de verdad necesitas dos &mut a la vez, pero apuntan a partes distintas de los datos. El compilador no siempre lo ve, así que se lo muestras tú.
Con dos elementos de un Vec o de un slice, tasks[0] y tasks[1] toman prestado tasks entero. Usa split_at_mut (en inglés) para cortarlo en dos mitades que garantizan no solaparse:
fn main() { let mut tasks = vec![String::from("escribir"), String::from("probar")];
// Divide el Vec en dos mitades que no se solapan let (left, right) = tasks.split_at_mut(1); let first = &mut left[0]; let second = &mut right[0];
std::mem::swap(first, second); println!("{tasks:?}");}Salida:
["probar", "escribir"](Para intercambiar en concreto, tasks.swap(0, 1) es todavía más sencillo. split_at_mut sirve para cuando necesitas las dos referencias para hacer trabajo de verdad).
Con un struct, el mismo error aparece cuando llamas a dos métodos que reciben &mut self, como p.name_mut() y p.score_mut(). Cada llamada toma prestado el struct entero. Si en cambio tomas prestados los campos directamente, el compilador ve que son independientes:
struct Player { name: String, score: u32,}
fn main() { let mut p = Player { name: String::from("Ferris"), score: 0 };
// Toma prestados los campos directamente, no a través de métodos let name = &mut p.name; let score = &mut p.score;
name.push_str(" el cangrejo"); *score += 10; println!("{} tiene {} puntos", p.name, p.score);}Salida:
Ferris el cangrejo tiene 10 puntosSolución 3: Usa índices en lugar de guardar referencias
Sección titulada «Solución 3: Usa índices en lugar de guardar referencias»Un clásico: recorres un Vec con iter_mut() y además intentas hacer push dentro del bucle. El iterador guarda un &mut durante todo el bucle, así que push sería un segundo:
fn main() { let mut queue = vec![1, 2, 3]; for job in queue.iter_mut() { *job *= 10; queue.push(*job); // ❌ E0499: iter_mut() ya tiene prestado `queue` }}Cuando el bucle tiene que cambiar la propia colección, recorre las posiciones. Un índice es solo un número, así que no toma nada prestado:
fn main() { let mut queue = vec![1, 2, 3];
// Recorre posiciones en lugar de guardar una referencia dentro de `queue` for i in 0..queue.len() { queue[i] *= 10; let job = queue[i]; queue.push(job); }
println!("{queue:?}");}Salida:
[10, 20, 30, 10, 20, 30]0..queue.len() se calcula una sola vez, antes de que empiece el bucle, así que los elementos nuevos no se vuelven a recorrer. Cada préstamo queue[i] dura solo una línea.
Solución 4: RefCell (último recurso)
Sección titulada «Solución 4: RefCell (último recurso)»RefCell traslada la comprobación de préstamos del momento de compilar al tiempo de ejecución. Puedes llamar a borrow_mut() sin una variable mut, siempre que no haya dos préstamos mutables vivos en el mismo instante:
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100); // fíjate: no hace falta `mut`
*balance.borrow_mut() += 50; // préstamo, cambio, y el préstamo termina en el `;` *balance.borrow_mut() -= 30;
println!("Balance: {}", balance.borrow());}Salida:
Balance: 120Sé sincero contigo: RefCell no elimina la regla, solo la comprueba más tarde. Si mantienes vivos dos borrow_mut() a la vez, el programa compila pero hace panic al ejecutarse:
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100);
let mut deposit = balance.borrow_mut(); let mut withdraw = balance.borrow_mut(); // 💥 `deposit` sigue vivo
*deposit += 50; *withdraw -= 30;}thread 'main' (12345) panicked at src/main.rs:7:32:RefCell already borrowedEl mensaje dice que el hilo principal hizo panic en la línea 7 porque el RefCell ya estaba prestado. Así que recurre a RefCell solo cuando los datos de verdad están compartidos (por ejemplo, dentro de Rc<RefCell<T>>) y las Soluciones 1–3 no encajan. No es una forma de hacer callar al compilador.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»- Guardar
&mutdentro de un bucle.handles.push(&mut counter)dentro de un bucleforfalla con “mutably borrowed here in the previous iteration of the loop” (prestado mutablemente aquí en la vuelta anterior del bucle). Cada vuelta añade otro&mutal mismo valor. Solución: guarda índices o valores propios, o cambia el valor directamente en el bucle. - Dos llamadas a
get_mutsobre unHashMap.let a = stock.get_mut("apples")y luegolet p = stock.get_mut("pears")toman prestadostockdos veces. Solución: termina conaantes de volver a llamar aget_mut, o usastock.get_disjoint_mut(["apples", "pears"])para obtener los dos a la vez. - Dos closures que cambian la misma variable.
let mut inc = || count += 1;ylet mut inc2 = || count += 2;guardan cada una un&mut countmientras existan. Solución: usa una sola closure, o llama a la primera por última vez antes de crear la segunda. Mira Closures.
Errores relacionados
Sección titulada «Errores relacionados»- E0502: cannot borrow as mutable because it is also borrowed as immutable: la misma idea, pero uno de los préstamos es un
&de solo lectura. - E0382: borrow of moved value: usaste un valor después de moverlo a otro sitio.
- E0106: missing lifetime specifier: Rust necesita saber cuánto vive una referencia que devuelves.
- 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. ¿Cuándo muestra Rust el error E0499?
2. Creas let a = &mut v;, usas a por última vez y luego creas let b = &mut v;. ¿Qué pasa?
3. ¿Qué herramienta te da dos referencias &mut a elementos distintos del mismo slice?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- E0499 — Rust error codes index (en inglés): la explicación oficial de este error, con un ejemplo mínimo.
- References and Borrowing — El libro de Rust (en inglés): el capítulo que presenta la regla de “solo un
&muta la vez”. También existe una traducción al español de la comunidad del libro. - Splitting Borrows — The Rustonomicon (en inglés): por qué los campos de un struct se pueden tomar prestados por separado y los elementos de un slice no, y cómo funciona
split_at_mut. - RefCell and Interior Mutability — El libro de Rust (en inglés): cuándo tiene sentido llevar la comprobación de préstamos al tiempo de ejecución.