Errore Rust E0594: cannot assign, as it is not declared as mutable (manca mut)
E0594 significa che hai scritto qualcosa = valore (oppure +=, -=, …), ma hai solo il permesso di leggere quel qualcosa. Di solito è il campo di una struct in una variabile senza mut, o un valore che raggiungi attraverso un riferimento &. È un errore molto comune, e la soluzione è quasi sempre una parola: mut. Questa pagina ti mostra prima la soluzione e poi te la spiega. Per gli altri errori di questa serie, vedi Errori del compilatore Rust.
Il messaggio di errore
Sezione intitolata “Il messaggio di errore”error[E0594]: cannot assign to `p.hp`, as `p` is not declared as mutable --> src/main.rs:8:5 |8 | p.hp = 5; // ❌ `p` is read-only | ^^^^^^^^ cannot assign |help: consider changing this to be mutable |7 | let mut p = Player { name: String::from("Ferris"), hp: 10 }; | +++Il compilatore dice: «non puoi assegnare a p.hp, perché p non è dichiarata mutabile». Questo è il programma che lo produce:
struct Player { name: String, hp: u32,}
fn main() { let p = Player { name: String::from("Ferris"), hp: 10 }; p.hp = 5; // ❌ `p` is read-only println!("{} has {} hp", p.name, p.hp);}Vedrai spesso anche una seconda formulazione: «cannot assign to *x, which is behind a & reference» (non puoi assegnare a *x, che sta dietro un riferimento &). Stesso codice di errore, causa leggermente diversa: lì hai in mano un riferimento &, e un riferimento & ti permette solo di guardare. Qui sotto trovi entrambi i casi.
Cosa significa
Sezione intitolata “Cosa significa”Guarda i ^^^^^^^^ sotto p.hp = 5 alla riga 8. Stai cambiando un campo di p. Ma p è stata creata con un semplice let, quindi tutta la struct è di sola lettura, compresi tutti i campi al suo interno.
La riga help: mostra la soluzione: let mut p. I +++ indicano i tre caratteri da aggiungere.
Con la seconda formulazione, il compilatore indica una riga come *x += 1 e aggiunge `x` is a `&` reference, so it cannot be written to (x è un riferimento &, quindi non ci si può scrivere). Non stai cambiando una variabile tua. Stai provando a cambiare il valore di qualcun altro attraverso un riferimento preso in prestito, di sola lettura.
Pensa a un libro della biblioteca. Puoi leggerlo quanto vuoi, ma non puoi scriverci sopra. Per prendere appunti ti serve una copia tua (let mut) o il permesso del proprietario (&mut).
Perché Rust si comporta così
Sezione intitolata “Perché Rust si comporta così”In Rust tutto è di sola lettura, a meno che tu non dica il contrario. Una variabile ha bisogno di let mut per cambiare, e un riferimento deve essere &mut per cambiare ciò a cui punta. Vedi Rendere mutabile una struct e Riferimenti mutabili.
In Rust non esiste il «campo mutabile». La mutabilità appartiene alla variabile (o al riferimento), non ai singoli campi. Quindi let mut p rende modificabili tutti i campi di p, e un semplice let p non ne rende modificabile nessuno. Così la regola resta semplice: guardi come è stato dichiarato un valore e sai se può cambiare.
La regola sui riferimenti ti protegge dalle sorprese. Se passi &player a una funzione, la sua firma promette che leggerà soltanto. Senza quella promessa, qualsiasi funzione potrebbe cambiare i tuoi dati di nascosto, e il compilatore non potrebbe impedire a chi legge e a chi scrive di toccare lo stesso valore nello stesso momento (è di questo che parla E0502).
Come risolverlo
Sezione intitolata “Come risolverlo”Soluzione 1: Aggiungi mut alla variabile
Sezione intitolata “Soluzione 1: Aggiungi mut alla variabile”Scegli questa quando la struct è tua e vuoi cambiarne i campi. Nella maggior parte dei casi è la soluzione giusta.
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!("{} ha {} punti vita", p.name, p.hp);}Output:
Ferris ha 8 punti vitaLo stesso vale per le tuple (pair.0 = 1;) e gli array (grid[2] = 7;): la variabile che li contiene ha bisogno di mut. Per una struct dentro un Vec, team[0].hp = 10; richiede let mut team; quel caso viene segnalato come E0596, ma la soluzione è la stessa.
Se assegni una volta sola, forse mut non ti serve proprio: costruisci la struct con il valore giusto fin dall’inizio, come Player { hp: 5, ..other }.
Soluzione 2: Cicla con &mut v o iter_mut()
Sezione intitolata “Soluzione 2: Cicla con &mut v o iter_mut()”Scegli questa quando scorri una collezione e vuoi cambiare ogni elemento. for x in &v e v.iter() ti danno riferimenti &, che sono di sola lettura:
fn main() { let scores = vec![10, 20, 30]; for x in &scores { *x += 1; // ❌ `x` is a `&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` is a `&i32` | ^^^^^^^ `x` is a `&` reference, so it cannot be written to |help: use a mutable iterator instead |3 | for x in &mut scores { | +++Cicla su &mut scores (o chiama .iter_mut()), e rendi mut anche il vettore:
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:?}");}Output:
[11, 21, 31][22, 42, 62]&mut scores e scores.iter_mut() fanno la stessa cosa; scegli quello che si legge meglio. Se non ti serve cambiare la lista sul posto, funziona anche costruirne una nuova: let bigger: Vec<i32> = scores.iter().map(|x| x + 1).collect();. Vedi Iteratori.
Soluzione 3: Usa &mut self o un parametro &mut
Sezione intitolata “Soluzione 3: Usa &mut self o un parametro &mut”Scegli questa quando un metodo o una funzione cambia i dati che ha ricevuto. Un metodo con &self può solo leggere la struct, quindi assegnare un campo fallisce:
impl Account { fn deposit(&self, amount: u64) { self.balance += amount; // ❌ `self` is a `&` reference }}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) { | +++Una funzione normale con un parametro & dà lo stesso errore: fn reset(score: &u32) { *score = 0; } fallisce con cannot assign to `*score`, which is behind a `&` reference. La soluzione è la stessa per entrambi: scrivi &mut nella firma, rendi let mut la variabile di chi chiama e passa &mut nella chiamata.
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!("Punteggio: {score}");}Output:
Saldo: 75Punteggio: 0Per i metodi, Rust scrive il &mut al posto tuo nella chiamata: acc.deposit(50) prende in prestito acc in modo mutabile da solo, purché acc sia let mut. Per le funzioni lo scrivi tu: reset(&mut score). Se passi &score a una funzione che vuole &mut u32, ottieni E0308 (types differ in mutability). Vedi Aggiungere comportamento con i metodi.
Soluzione 4 (ultima risorsa): Cell o RefCell
Sezione intitolata “Soluzione 4 (ultima risorsa): Cell o RefCell”Scegli questa solo quando non puoi ottenere &mut: per esempio, implementi un trait il cui metodo è fissato come &self, oppure molte parti del programma condividono un valore tramite Rc. Cell e RefCell permettono modifiche attraverso un riferimento & («mutabilità interna», 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("mela"), String::from("pera")], lookups: Cell::new(0), history: RefCell::new(Vec::new()), }; println!("pera? {}", catalog.contains("pera")); println!("kiwi? {}", catalog.contains("kiwi")); println!("Ricerche: {}", catalog.lookups.get()); println!("Cronologia: {:?}", catalog.history.borrow());}Output:
pera? truekiwi? falseRicerche: 2Cronologia: ["pera", "kiwi"]Cell è per piccoli valori Copy come i contatori; RefCell per tutto il resto. Sii onesto sul costo: RefCell sposta il controllo dei prestiti dalla compilazione all’esecuzione, e va in panic se prendi in prestito in modo mutabile due volte contemporaneamente. Se un semplice &mut funziona, preferiscilo. Tra thread diversi, usa invece Mutex o gli atomici (vedi Stato condiviso). Trovi di più in RefCell.
Dove lo incontri di solito
Sezione intitolata “Dove lo incontri di solito”Cambiare un valore dentro una HashMap. *stock.get("apple").unwrap() += 1; fallisce con cannot assign to data in a `&` reference, perché get restituisce un riferimento &. Soluzione: *stock.get_mut("apple").unwrap() += 1;, oppure *stock.entry("apple").or_insert(0) += 1;. Vedi HashMap.
Una closure passata come Fn che cambia una variabile. run(|| count += 1), dove run prende F: Fn(), fallisce con cannot assign to `count`, as it is a captured variable in a `Fn` closure. Soluzione: cambia il vincolo in F: FnMut() (e usa mut f dentro run). Vedi Quando scegli il vincolo sbagliato.
Un contatore globale in uno static. static VISITS: u32 = 0; e poi VISITS += 1; fallisce con cannot assign to immutable static item `VISITS`. Soluzione: usa static VISITS: AtomicU32 = AtomicU32::new(0); e VISITS.fetch_add(1, Ordering::Relaxed);, o meglio ancora, passa un contatore in giro come &mut.
Errori correlati
Sezione intitolata “Errori correlati”- E0596: cannot borrow as mutable. Lo stesso
mutmancante, ma per una chiamata di metodo comelist.push(x)invece che per un’assegnazione. - E0384: cannot assign twice to immutable variable. Assegni un’intera variabile (
x = 5;) invece di un campo o attraverso un riferimento. - E0308: mismatched types,
types differ in mutability, quando passi&xdove ci si aspetta&mut x. - E0502: hai l’accesso
&mut, ma qualcos’altro sta ancora leggendo il valore. - E0507: cannot move out of a value behind a reference. Di nuovo la regola della «sola lettura», questa volta per gli spostamenti invece che per le scritture.
- Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. let user = User { age: 30 }; user.age = 31; dà E0594. Qual è la soluzione?
2. for p in &prices { *p += 1; } fallisce. Cosa cambi?
3. Un metodo fn rename(&self, n: String) { self.name = n; } dà E0594. Qual è la soluzione abituale?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- E0594 — Rust error codes index (in inglese): la spiegazione ufficiale di questo errore.
- Defining and Instantiating Structs — The Rust Book (in inglese): perché deve essere mutabile l’intera istanza, non i singoli campi.
- References and Borrowing — The Rust Book (in inglese): la parte «Mutable References» spiega
&contro&mut. - Method Syntax — The Rust Book (in inglese): quando un metodo ha bisogno di
&self,&mut selfoself. - RefCell and Interior Mutability — The Rust Book (in inglese): come
RefCellpermette modifiche attraverso&, e il suo costo in esecuzione.