Salta ai contenuti

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.

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.

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

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

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 vita
▶ Provalo tu

Lo 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 }.

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 `&` references
4 | *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]
▶ Provalo tu

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

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: 75
Punteggio: 0
▶ Provalo tu

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

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? true
kiwi? false
Ricerche: 2
Cronologia: ["pera", "kiwi"]
▶ Provalo tu

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.

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.

  • E0596: cannot borrow as mutable. Lo stesso mut mancante, ma per una chiamata di metodo come list.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 &x dove 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

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