Salta ai contenuti

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

E0596 significa che un pezzo di codice vuole cambiare un valore (gli serve un prestito mutabile, &mut), ma tu gli hai dato solo il permesso di leggerlo. Di solito hai dimenticato mut su un let, oppure una funzione prende & dove le servirebbe &mut. È molto comune, e la riga help: del compilatore ti dice quasi sempre esattamente dove aggiungere 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[E0596]: cannot borrow `tasks` as mutable, as it is not declared as mutable
--> src/main.rs:3:5
|
3 | tasks.push("write tests"); // ❌ push needs to change `tasks`
| ^^^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
2 | let mut tasks = Vec::new();
| +++

Il compilatore dice: «non puoi prendere in prestito tasks come mutabile, perché non è dichiarata mutabile». Questo è il programma che lo produce:

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

Vedrai anche una seconda formulazione: «cannot borrow *tasks as mutable, as it is behind a & reference» (non puoi prenderlo in prestito come mutabile, perché sta dietro un riferimento &). Stesso codice di errore, causa leggermente diversa: lì la variabile è un riferimento &, quindi non puoi cambiare ciò a cui punta. Qui sotto trovi entrambi i casi.

Guarda i ^^^^^ sotto tasks alla riga 3. Per aggiungere un elemento, Vec::push ha bisogno di un prestito mutabile del vettore: la sua firma è fn push(&mut self, value: T). Ma tasks è stata creata con un semplice let, quindi è di sola lettura. Rust non può darti un &mut su qualcosa che hai detto che non sarebbe mai cambiato.

La riga help: mostra la soluzione: let mut tasks. I +++ indicano i tre caratteri da aggiungere.

Pensa a un documento condiviso con accesso «solo visualizzazione». Puoi leggere ogni parola, ma il pulsante «modifica» è grigio. mut (o &mut) è il modo in cui chiedi l’accesso in modifica.

In Rust le variabili sono immutabili per default, e anche i riferimenti sono di sola lettura per default. Per cambiare qualcosa devi chiederlo esplicitamente: let mut per una variabile che possiedi, &mut per un riferimento a qualcosa che hai preso in prestito. Vedi Variabili e Prestito (borrowing).

Così il codice è più facile da capire e da fidarsene. Se una funzione prende &Vec<String>, sai già dalla firma che non cambierà la tua lista. Se prende &mut Vec<String>, sai che potrebbe farlo. Niente cambia alle tue spalle.

La stessa regola rende possibili le garanzie di sicurezza più grandi di Rust. Visto che il «può cambiare» è sempre scritto nel codice, il compilatore può assicurarsi che non ci siano mai uno che scrive e uno che legge nello stesso momento (sono E0502 ed E0499). E0596 è il primo passo: devi chiedere di scrivere, prima che qualcuno controlli se scrivere è sicuro.

Scegli questa quando il valore è tuo e deve cambiare: un Vec in cui fai push, una String a cui aggiungi testo, o una struct di cui chiami un metodo &mut self. Nella maggior parte dei casi è la soluzione giusta.

struct Counter {
count: u32,
}
impl Counter {
fn increment(&mut self) {
self.count += 1;
}
}
fn main() {
let mut tasks = Vec::new();
tasks.push("scrivere i test");
tasks.push("correggere il bug");
println!("{tasks:?}");
let mut counter = Counter { count: 0 };
counter.increment();
counter.increment();
println!("Conteggio: {}", counter.count);
}

Output:

["scrivere i test", "correggere il bug"]
Conteggio: 2
▶ Provalo tu

Senza mut su counter, counter.increment() fallisce con cannot borrow counter as mutable. Chiamare un metodo che prende &mut self prende in prestito in modo mutabile tutta la variabile, quindi la variabile deve essere mut. I metodi che prendono solo &self funzionano benissimo anche senza. Vedi Aggiungere comportamento con i metodi.

Lo stesso vale per un parametro che la funzione possiede: fn shout(s: String) non può chiamare s.push('!'), mentre fn shout(mut s: String) sì.

Soluzione 2: Cambia il parametro in &mut e passa &mut x

Sezione intitolata “Soluzione 2: Cambia il parametro in &mut e passa &mut x”

Scegli questa quando una funzione deve cambiare qualcosa che appartiene a chi la chiama. Questa versione produce la formulazione «behind a & reference»:

fn add_task(tasks: &Vec<String>, task: &str) {
tasks.push(task.to_string()); // ❌ `tasks` is a `&` reference
}
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` is a `&` reference
| ^^^^^ `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) {
| +++

Correggilo in tre punti: il tipo del parametro diventa &mut Vec<String>, la variabile di chi chiama diventa let mut, e la chiamata passa &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, "scrivere i test");
add_task(&mut tasks, "aggiornare la documentazione");
println!("{} attività: {tasks:?}", tasks.len());
}

Output:

2 attività: ["scrivere i test", "aggiornare la documentazione"]
▶ Provalo tu

Se cambi il parametro ma continui a passare &tasks, ottieni invece E0308, con la nota types differ in mutability (i tipi differiscono nella mutabilità). Vuol dire solo: scrivi &mut tasks anche nella chiamata. Scrivere &mut nel punto della chiamata è voluto: chiunque legga main vede che add_task può cambiare tasks.

Scegli questa quando una closure cambia una variabile che ha catturato. Una closure del genere cambia il proprio stato ogni volta che viene eseguita, quindi per chiamarla serve un prestito mutabile della closure stessa:

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;
| +++

La variabile clicks è già mut. Quello che manca è mut sulla closure:

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

Output:

Clic: 3
▶ Provalo tu

Questo sorprende molti, perché la closure non «sembra» una cosa che cambia. Ma dentro tiene un &mut clicks, quindi è una closure FnMut, e per chiamare una FnMut serve mut. Vedi Closure che modificano le variabili catturate e Fn, FnMut e FnOnce.

Scegli questa quando scorri una collezione e vuoi cambiare ogni elemento. .iter() e for x in &list ti danno riferimenti &, che sono di sola lettura:

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

Passa a .iter_mut() o a &mut names (e rendi mut anche names):

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

Output:

["ferris!", "corro!"]
[15, 25, 35]
▶ Provalo tu

Lo suggerisce anche il compilatore: help: use mutable method ... names.iter_mut(). Lo stesso vale per le closure nelle catene di iteratori: words.iter().for_each(|w| w.push('!')) fallisce, words.iter_mut().for_each(|w| w.push('!')) funziona. Vedi Iteratori.

Costruire un Vec o una String un pezzo alla volta. let list = Vec::new(); e poi list.push(...), oppure let s = String::new(); e poi s.push_str(...). Soluzione: let mut. Se hai già tutti gli elementi all’inizio, let list = vec![a, b, c]; non ha bisogno di mut.

Chiamare un metodo «setter» o di aggiornamento su una struct. account.deposit(50), dove deposit prende &mut self, fallisce se non hai scritto let mut account. Soluzione: let mut account = Account::new();. Vedi Struct.

Funzioni di supporto che prendono & per abitudine. Scrivi fn reset(board: &Board) e poi provi a cambiare board all’interno. Soluzione: cambialo in board: &mut Board e chiama reset(&mut board). Una buona abitudine: scrivi & quando la funzione legge soltanto, &mut quando cambia qualcosa.

Cambiare un valore dentro una HashMap. if let Some(v) = map.get("key") { v.push(..) } fallisce, perché get restituisce un riferimento &. Soluzione: usa map.get_mut("key"), oppure l’API entry. Vedi HashMap.

  • E0384: cannot assign twice to immutable variable. Lo stesso mut mancante, ma per x = ... o x += 1 invece che per una chiamata di metodo.
  • E0594: non puoi assegnare a un campo o attraverso un riferimento &, per esempio p.hp = 5; quando p non è mut, oppure *x += 1 in for x in &v. Stesse soluzioni: let mut, oppure &mut.
  • E0308: mismatched types, types differ in mutability, quando passi &x a una funzione che vuole &mut x.
  • E0502: hai ottenuto l’accesso &mut, ma qualcos’altro sta ancora leggendo il valore.
  • E0499: due prestiti &mut dello stesso valore nello stesso momento.
  • Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.

Verifica veloce

1. let scores = Vec::new(); scores.push(10); dà E0596. Qual è la soluzione?

2. Una funzione fn add(list: &Vec<u32>) chiama list.push(1). Cosa cambi?

3. let mut total = 0; let add = |x| total += x; add(5); fallisce. Perché?

Punteggio: 0 / 3