Error E0594 de Rust: cannot assign, as it is not declared as mutable (sin mut)
E0594 significa que escribiste algo = valor (o +=, -=, …), pero ese algo solo tienes permiso para leerlo. Normalmente es un campo de un struct guardado en una variable sin mut, o un valor al que llegas a través de una referencia &. Es un error muy común, y la solución casi siempre es una sola palabra: 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.
El mensaje de error
Sección titulada «El mensaje de error»error[E0594]: cannot assign to `p.hp`, as `p` is not declared as mutable --> src/main.rs:8:5 |8 | p.hp = 5; // ❌ `p` es de solo lectura | ^^^^^^^^ cannot assign |help: consider changing this to be mutable |7 | let mut p = Player { name: String::from("Ferris"), hp: 10 }; | +++El compilador te dice que no puedes asignar a p.hp porque p no se declaró como mutable, y te sugiere hacerla mutable. Este es el programa que lo provoca:
struct Player { name: String, hp: u32,}
fn main() { let p = Player { name: String::from("Ferris"), hp: 10 }; p.hp = 5; // ❌ `p` es de solo lectura println!("{} has {} hp", p.name, p.hp);}También verás a menudo un segundo texto: “cannot assign to *x, which is behind a & reference” (no se puede asignar a *x, que está detrás de una referencia &). Es el mismo código de error con una causa algo distinta: ahí tienes una referencia &, y una referencia & solo te deja mirar. Más abajo verás los dos casos.
Qué significa
Sección titulada «Qué significa»Mira los ^^^^^^^^ debajo de p.hp = 5 en la línea 8. Estás cambiando un campo de p. Pero p se creó con un let normal, así que todo el struct es de solo lectura, y eso incluye cada uno de sus campos.
La línea help: muestra la solución: let mut p. Los +++ marcan los tres caracteres que hay que añadir.
Con el segundo texto, el compilador señala una línea como *x += 1 y añade `x` is a `&` reference, so it cannot be written to (x es una referencia &, así que no se puede escribir en ella). No estás cambiando una variable tuya. Estás intentando cambiar el valor de otro a través de una referencia prestada de solo lectura.
Piensa en un libro de la biblioteca. Puedes leerlo todo lo que quieras, pero no puedes escribir en él. Para tomar notas necesitas tu propia copia (let mut) o el permiso del dueño (&mut).
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»En Rust, todo es de solo lectura a menos que digas lo contrario. Una variable necesita let mut para cambiar, y una referencia tiene que ser &mut para cambiar aquello a lo que apunta. Mira Hacer un struct mutable y Referencias mutables.
En Rust no existen los “campos mutables”. La mutabilidad pertenece a la variable (o a la referencia), no a cada campo. Así que let mut p hace modificables todos los campos de p, y un simple let p no deja cambiar ninguno. Eso mantiene la regla sencilla: mira cómo se declaró un valor y sabrás si puede cambiar.
La regla de las referencias te protege de sorpresas. Si pasas &player a una función, su firma promete que solo va a leer. Sin esa promesa, cualquier función podría cambiar tus datos sin avisar, y el compilador no podría impedir que alguien lea y alguien escriba el mismo valor a la vez (de eso trata E0502).
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»Solución 1: Añade mut a la variable
Sección titulada «Solución 1: Añade mut a la variable»Elige esta cuando el struct es tuyo y quieres cambiar sus campos. Es la solución correcta la mayoría de las veces.
struct Player { name: String, hp: u32,}
fn main() { let mut p = Player { name: String::from("Ferris"), hp: 10 }; p.hp = 5; p.hp += 3; println!("{} tiene {} puntos de vida", p.name, p.hp);}Salida:
Ferris tiene 8 puntos de vidaLo mismo vale para las tuplas (pair.0 = 1;) y los arrays (grid[2] = 7;): la variable que los guarda necesita mut. Para un struct dentro de un Vec, team[0].hp = 10; necesita let mut team; ese caso se informa como E0596, pero la solución es la misma.
Si solo asignas una vez, quizá no necesites mut en absoluto: crea el struct con el valor correcto desde el principio, por ejemplo Player { hp: 5, ..other }.
Solución 2: Recorre con &mut v o iter_mut()
Sección titulada «Solución 2: Recorre con &mut v o iter_mut()»Elige esta cuando recorres una colección y quieres cambiar cada elemento. for x in &v y v.iter() te dan referencias &, que son de solo lectura:
fn main() { let scores = vec![10, 20, 30]; for x in &scores { *x += 1; // ❌ `x` es un `&i32` } println!("{scores:?}");}error[E0594]: cannot assign to `*x`, which is behind a `&` reference --> src/main.rs:4:9 |3 | for x in &scores { | ------- this iterator yields `&` references4 | *x += 1; // ❌ `x` es un `&i32` | ^^^^^^^ `x` is a `&` reference, so it cannot be written to |help: use a mutable iterator instead |3 | for x in &mut scores { | +++El compilador te avisa de que este iterador produce referencias & y te sugiere usar un iterador mutable. Recorre &mut scores (o llama a .iter_mut()) y haz que el propio vector sea mut:
fn main() { let mut scores = vec![10, 20, 30]; for x in &mut scores { *x += 1; } println!("{scores:?}");
for x in scores.iter_mut() { *x *= 2; } println!("{scores:?}");}Salida:
[11, 21, 31][22, 42, 62]&mut scores y scores.iter_mut() hacen lo mismo; elige el que se lea mejor. Si no necesitas cambiar la lista en el sitio, también puedes construir una nueva: let bigger: Vec<i32> = scores.iter().map(|x| x + 1).collect();. Mira Iteradores.
Solución 3: Usa &mut self o un parámetro &mut
Sección titulada «Solución 3: Usa &mut self o un parámetro &mut»Elige esta cuando un método o una función cambia los datos que recibió. Un método con &self solo puede leer el struct, así que asignar a un campo falla:
impl Account { fn deposit(&self, amount: u64) { self.balance += amount; // ❌ `self` es una referencia `&` }}error[E0594]: cannot assign to `self.balance`, which is behind a `&` reference --> src/main.rs:7:9 |7 | self.balance += amount; | ^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so it cannot be written to |help: consider changing this to be a mutable reference |6 | fn deposit(&mut self, amount: u64) { | +++Aquí self es una referencia &, así que no se puede escribir a través de ella; el compilador te propone convertirla en una referencia mutable. Una función normal con un parámetro & da el mismo error: fn reset(score: &u32) { *score = 0; } falla con cannot assign to `*score`, which is behind a `&` reference. La solución es la misma en los dos casos: pon &mut en la firma, haz que la variable de quien llama sea let mut y pasa &mut en la llamada.
struct Account { balance: u64,}
impl Account { fn deposit(&mut self, amount: u64) { self.balance += amount; }}
fn reset(score: &mut u32) { *score = 0;}
fn main() { let mut acc = Account { balance: 0 }; acc.deposit(50); acc.deposit(25); println!("Saldo: {}", acc.balance);
let mut score = 42; reset(&mut score); println!("Puntos: {score}");}Salida:
Saldo: 75Puntos: 0En los métodos, Rust escribe el &mut por ti en la llamada: acc.deposit(50) toma prestado acc de forma mutable automáticamente, siempre que acc sea let mut. En las funciones lo escribes tú: reset(&mut score). Si pasas &score a una función que espera &mut u32, obtienes E0308 (types differ in mutability). Mira Añadir comportamiento con métodos.
Solución 4 (último recurso): Cell o RefCell
Sección titulada «Solución 4 (último recurso): Cell o RefCell»Elige esta solo cuando no puedes conseguir un &mut: por ejemplo, cuando implementas un trait cuyo método está fijado como &self, o cuando muchas partes del programa comparten un valor a través de Rc. Cell y RefCell permiten cambios a través de una referencia & (lo que se llama “mutabilidad interior”, interior mutability):
use std::cell::{Cell, RefCell};
struct Catalog { items: Vec<String>, lookups: Cell<u32>, history: RefCell<Vec<String>>,}
impl Catalog { fn contains(&self, name: &str) -> bool { self.lookups.set(self.lookups.get() + 1); self.history.borrow_mut().push(name.to_string()); self.items.iter().any(|item| item == name) }}
fn main() { let catalog = Catalog { items: vec![String::from("manzana"), String::from("pera")], lookups: Cell::new(0), history: RefCell::new(Vec::new()), }; println!("¿pera? {}", catalog.contains("pera")); println!("¿kiwi? {}", catalog.contains("kiwi")); println!("Búsquedas: {}", catalog.lookups.get()); println!("Historial: {:?}", catalog.history.borrow());}Salida:
¿pera? true¿kiwi? falseBúsquedas: 2Historial: ["pera", "kiwi"]Cell es para valores pequeños que son Copy, como contadores; RefCell es para todo lo demás. Pero ojo con el coste: RefCell pasa la comprobación de préstamos del momento de compilar al momento de ejecutar, y provoca un panic si tomas prestado de forma mutable dos veces a la vez. Si un &mut normal te sirve, prefiérelo. Entre hilos, usa Mutex o atómicos (mira Estado compartido). Hay más en RefCell.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»Al cambiar un valor dentro de un HashMap. *stock.get("apple").unwrap() += 1; falla con cannot assign to data in a `&` reference, porque get devuelve una referencia &. Solución: *stock.get_mut("apple").unwrap() += 1; o *stock.entry("apple").or_insert(0) += 1;. Mira HashMaps.
Una closure pasada como Fn que cambia una variable. run(|| count += 1), cuando run recibe F: Fn(), falla con cannot assign to `count`, as it is a captured variable in a `Fn` closure (no se puede asignar a count porque es una variable capturada en una closure Fn). Solución: cambia la restricción a F: FnMut() (y usa mut f dentro de run). Mira Cuando eliges la restricción equivocada.
Un contador global en un static. static VISITS: u32 = 0; y luego VISITS += 1; falla con cannot assign to immutable static item `VISITS`. Solución: usa static VISITS: AtomicU32 = AtomicU32::new(0); y VISITS.fetch_add(1, Ordering::Relaxed); o, mejor aún, pasa un contador de un lado a otro como &mut.
Errores relacionados
Sección titulada «Errores relacionados»- E0596: cannot borrow as mutable (no se puede tomar prestado como mutable). El mismo
mutolvidado, pero para una llamada a un método comolist.push(x)en lugar de una asignación. - E0384: cannot assign twice to immutable variable. Asignas a una variable entera (
x = 5;) en lugar de a un campo o a través de una referencia. - E0308: mismatched types,
types differ in mutability, cuando pasas&xdonde se espera&mut x. - E0502: tienes acceso
&mut, pero otra cosa sigue leyendo el valor. - E0507: cannot move out of a value behind a reference (no se puede mover un valor que está detrás de una referencia). Otra vez la regla de “solo lectura”, esta vez para los moves en lugar de las escrituras.
- 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. let user = User { age: 30 }; user.age = 31; da E0594. ¿Cuál es la solución?
2. for p in &prices { *p += 1; } falla. ¿Qué cambias?
3. Un método fn rename(&self, n: String) { self.name = n; } da E0594. ¿Cuál es la solución habitual?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- E0594 — Rust error codes index (en inglés): la explicación oficial de este error.
- Defining and Instantiating Structs — El libro de Rust (en inglés): por qué tiene que ser mutable la instancia entera y no campos sueltos.
- References and Borrowing — El libro de Rust (en inglés): la parte “Mutable References” explica
&frente a&mut. - Method Syntax — El libro de Rust (en inglés): cuándo un método necesita
&self,&mut selfoself. - RefCell and Interior Mutability — El libro de Rust (en inglés): cómo
RefCellpermite cambios a través de&, y su coste en tiempo de ejecución.