Ir al contenido

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

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 here
5 | let withdraw = &mut balance;
| ^^^^^^^^^^^^ second mutable borrow occurs here
6 |
7 | *deposit += 50;
| -------------- first borrow later used here

En 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}");
}

El compilador señala tres sitios, y juntos cuentan toda la historia:

  1. Línea 4: deposit se convierte en un préstamo mutable de balance.
  2. Línea 5: withdraw intenta convertirse en un segundo préstamo mutable de balance.
  3. Línea 7: deposit se 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.

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.

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: 120
▶ Pruébalo tú mismo

Muchas 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"]
▶ Pruébalo tú mismo

(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 puntos
▶ Pruébalo tú mismo

Solució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]
▶ Pruébalo tú mismo

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.

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: 120
▶ Pruébalo tú mismo

Sé 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 borrowed

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

  • Guardar &mut dentro de un bucle. handles.push(&mut counter) dentro de un bucle for falla 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 &mut al mismo valor. Solución: guarda índices o valores propios, o cambia el valor directamente en el bucle.
  • Dos llamadas a get_mut sobre un HashMap. let a = stock.get_mut("apples") y luego let p = stock.get_mut("pears") toman prestado stock dos veces. Solución: termina con a antes de volver a llamar a get_mut, o usa stock.get_disjoint_mut(["apples", "pears"]) para obtener los dos a la vez.
  • Dos closures que cambian la misma variable. let mut inc = || count += 1; y let mut inc2 = || count += 2; guardan cada una un &mut count mientras existan. Solución: usa una sola closure, o llama a la primera por última vez antes de crear la segunda. Mira Closures.

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