Ir al contenido

Error E0596 de Rust: cannot borrow as mutable, as it is not declared as mutable

E0596 significa que cierto código quiere cambiar un valor (necesita un préstamo mutable, &mut), pero tú solo le diste permiso para leerlo. Normalmente se te olvidó mut en un let, o una función recibe & cuando necesita &mut. Es un error muy común, y la línea help: del compilador casi siempre te dice exactamente dónde añadir mut. En esta página verás primero la solución y después la explicación. Para ver los demás errores de esta serie, visita Errores del compilador de Rust.

error[E0596]: cannot borrow `tasks` as mutable, as it is not declared as mutable
--> src/main.rs:3:5
|
3 | tasks.push("write tests"); // ❌ push necesita cambiar `tasks`
| ^^^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
2 | let mut tasks = Vec::new();
| +++

El compilador te dice que no puede tomar prestado tasks como mutable porque no se declaró como mutable, y te sugiere cambiarlo. Este es el programa que lo provoca:

fn main() {
let tasks = Vec::new();
tasks.push("write tests"); // ❌ push necesita cambiar `tasks`
println!("{tasks:?}");
}

También verás un segundo texto: “cannot borrow *tasks as mutable, as it is behind a & reference” (no se puede tomar prestado como mutable porque está detrás de una referencia &). Es el mismo código de error con una causa algo distinta: ahí la variable es una referencia &, así que no puedes cambiar aquello a lo que apunta. Más abajo verás los dos casos.

Mira los ^^^^^ debajo de tasks en la línea 3. Para añadir un elemento, Vec::push necesita un préstamo mutable del vector: su firma es fn push(&mut self, value: T). Pero tasks se creó con un let normal, así que es de solo lectura. Rust no puede darte un &mut de algo que dijiste que nunca iba a cambiar.

La línea help: muestra la solución: let mut tasks. Los +++ marcan los tres caracteres que hay que añadir.

Piensa en un documento compartido con acceso de “solo lectura”. Puedes leer cada palabra, pero el botón de “editar” está desactivado. mut (o &mut) es la forma de pedir permiso de edición.

En Rust, las variables son inmutables por defecto, y las referencias son de solo lectura por defecto. Para cambiar algo tienes que pedirlo de forma explícita: let mut para una variable que es tuya y &mut para una referencia a algo que tomaste prestado. Mira Variables y Borrowing.

Esto hace que el código sea más fácil de entender y de confiar. Si una función recibe &Vec<String>, sabes solo por su firma que no va a cambiar tu lista. Si recibe &mut Vec<String>, sabes que puede hacerlo. Nada cambia a tus espaldas.

Esta misma regla hace posibles las garantías de seguridad más grandes de Rust. Como “puede cambiar” siempre queda escrito, el compilador puede asegurarse de que nunca haya alguien escribiendo y alguien leyendo a la vez (de eso tratan E0502 y E0499). E0596 es el primer paso: tienes que pedir permiso para escribir antes de que nadie compruebe si escribir es seguro.

Elige esta cuando el valor es tuyo y debe cambiar: un Vec al que le añades elementos, un String al que le agregas texto, o un struct al que le llamas un método con &mut self. Es la solución correcta la mayoría de las veces.

struct Counter {
count: u32,
}
impl Counter {
fn increment(&mut self) {
self.count += 1;
}
}
fn main() {
let mut tasks = Vec::new();
tasks.push("escribir tests");
tasks.push("arreglar bug");
println!("{tasks:?}");
let mut counter = Counter { count: 0 };
counter.increment();
counter.increment();
println!("Contador: {}", counter.count);
}

Salida:

["escribir tests", "arreglar bug"]
Contador: 2
▶ Pruébalo tú mismo

Sin mut en counter, counter.increment() falla con cannot borrow counter as mutable. Llamar a un método que recibe &mut self toma prestada toda la variable de forma mutable, así que la variable tiene que ser mut. Los métodos que solo reciben &self funcionan bien sin él. Mira Añadir comportamiento con métodos.

Lo mismo pasa con un parámetro que pertenece a la función: fn shout(s: String) no puede llamar a s.push('!'), pero fn shout(mut s: String) sí.

Solución 2: Cambia el parámetro a &mut y pasa &mut x

Sección titulada «Solución 2: Cambia el parámetro a &mut y pasa &mut x»

Elige esta cuando una función necesita cambiar algo que pertenece a quien la llama. Esta versión da el texto “behind a & reference”:

fn add_task(tasks: &Vec<String>, task: &str) {
tasks.push(task.to_string()); // ❌ `tasks` es una referencia `&`
}
error[E0596]: cannot borrow `*tasks` as mutable, as it is behind a `&` reference
--> src/main.rs:2:5
|
2 | tasks.push(task.to_string()); // ❌ `tasks` es una referencia `&`
| ^^^^^ `tasks` is a `&` reference, so it cannot be borrowed as mutable
|
help: consider changing this to be a mutable reference
|
1 | fn add_task(tasks: &mut Vec<String>, task: &str) {
| +++

El compilador te explica que tasks es una referencia & y por eso no se puede tomar prestada como mutable, y te propone convertirla en una referencia mutable. Arréglalo en tres sitios: el tipo del parámetro pasa a ser &mut Vec<String>, la variable de quien llama pasa a ser let mut, y la llamada pasa &mut tasks:

fn add_task(tasks: &mut Vec<String>, task: &str) {
tasks.push(task.to_string());
}
fn main() {
let mut tasks = Vec::new();
add_task(&mut tasks, "escribir tests");
add_task(&mut tasks, "actualizar docs");
println!("{} tareas: {tasks:?}", tasks.len());
}

Salida:

2 tareas: ["escribir tests", "actualizar docs"]
▶ Pruébalo tú mismo

Si cambias el parámetro pero sigues pasando &tasks, obtienes E0308 con la nota types differ in mutability (los tipos difieren en mutabilidad). Eso solo significa: escribe también &mut tasks en la llamada. Escribir &mut en la llamada es a propósito; así, cualquiera que lea main ve que add_task puede cambiar tasks.

Solución 3: Haz mut la variable de la closure

Sección titulada «Solución 3: Haz mut la variable de la closure»

Elige esta cuando una closure cambia una variable que capturó. Una closure así cambia su propio estado cada vez que se ejecuta, así que para llamarla hace falta un préstamo mutable de la propia closure:

fn main() {
let mut clicks = 0;
let click = || clicks += 1;
click(); // ❌ cannot borrow `click` as mutable
click();
println!("Clicks: {clicks}");
}
error[E0596]: cannot borrow `click` as mutable, as it is not declared as mutable
--> src/main.rs:3:9
|
3 | let click = || clicks += 1;
| ^^^^^ ------ calling `click` requires mutable binding due to mutable borrow of `clicks`
| |
| not mutable
4 | click(); // ❌ cannot borrow `click` as mutable
| ----- cannot borrow as mutable
5 | click();
| ----- cannot borrow as mutable
|
help: consider changing this to be mutable
|
3 | let mut click = || clicks += 1;
| +++

El compilador explica que llamar a click requiere que sea mutable, porque toma prestado clicks de forma mutable. La variable clicks ya es mut. Lo que falta es mut en la closure:

fn main() {
let mut clicks = 0;
let mut click = || clicks += 1;
click();
click();
click();
println!("Clics: {clicks}");
}

Salida:

Clics: 3
▶ Pruébalo tú mismo

Esto sorprende a mucha gente, porque la closure no “parece” que cambie. Pero guarda dentro un &mut clicks, así que es una closure FnMut, y para llamar a una FnMut hace falta mut. Mira Closures que modifican las variables capturadas y Fn, FnMut y FnOnce.

Solución 4: Usa iter_mut() o &mut en un bucle

Sección titulada «Solución 4: Usa iter_mut() o &mut en un bucle»

Elige esta cuando recorres una colección y quieres cambiar cada elemento. .iter() y for x in &list te dan referencias &, que son de solo lectura:

for name in names.iter() {
name.push('!'); // ❌ cannot borrow `*name` as mutable, as it is behind a `&` reference
}

Cámbialo por .iter_mut() o &mut names (y haz que names también sea mut):

fn main() {
let mut names = vec![String::from("ferris"), String::from("corro")];
for name in names.iter_mut() {
name.push('!');
}
println!("{names:?}");
let mut prices = vec![10, 20, 30];
for price in &mut prices {
*price += 5;
}
println!("{prices:?}");
}

Salida:

["ferris!", "corro!"]
[15, 25, 35]
▶ Pruébalo tú mismo

El compilador también lo sugiere: help: use mutable method ... names.iter_mut(). Lo mismo vale para las closures en cadenas de iteradores: words.iter().for_each(|w| w.push('!')) falla y words.iter_mut().for_each(|w| w.push('!')) funciona. Mira Iteradores.

Al construir un Vec o un String paso a paso. let list = Vec::new(); y luego list.push(...), o let s = String::new(); y luego s.push_str(...). Solución: let mut. Si tienes todos los elementos desde el principio, let list = vec![a, b, c]; no necesita mut en absoluto.

Al llamar a un método “setter” o de actualización de un struct. account.deposit(50), cuando deposit recibe &mut self, falla a menos que hayas escrito let mut account. Solución: let mut account = Account::new();. Mira Structs.

Funciones auxiliares que reciben & por costumbre. Escribes fn reset(board: &Board) y luego intentas cambiar board dentro. Solución: cámbialo a board: &mut Board y llama a reset(&mut board). Un buen hábito: escribe & cuando la función solo lee y &mut cuando cambia algo.

Al cambiar un valor dentro de un HashMap. if let Some(v) = map.get("key") { v.push(..) } falla, porque get devuelve una referencia &. Solución: usa map.get_mut("key") o la API entry. Mira HashMaps.

  • E0384: cannot assign twice to immutable variable (no se puede asignar dos veces a una variable inmutable). El mismo mut olvidado, pero para x = ... o x += 1 en lugar de una llamada a un método.
  • E0594: no se puede asignar a un campo o a través de una referencia &, por ejemplo p.hp = 5; cuando p no es mut, o *x += 1 en for x in &v. Las mismas soluciones: let mut o &mut.
  • E0308: mismatched types, types differ in mutability (tipos que no coinciden), cuando pasas &x a una función que espera &mut x.
  • E0502: conseguiste acceso &mut, pero otra cosa sigue leyendo el valor.
  • E0499: dos préstamos &mut del mismo valor al mismo tiempo.
  • Todos los errores del compilador: la lista completa de páginas de errores.

Comprueba lo aprendido

1. let scores = Vec::new(); scores.push(10); da E0596. ¿Cuál es la solución?

2. Una función fn add(list: &Vec<u32>) llama a list.push(1). ¿Qué cambias?

3. let mut total = 0; let add = |x| total += x; add(5); falla. ¿Por qué?

Puntuación: 0 / 3