Errore Rust E0499: cannot borrow as mutable more than once at a time
L’errore E0499 significa che hai provato ad avere due riferimenti mutabili (&mut) allo stesso valore nello stesso momento. È uno degli errori del borrow checker più comuni, e di solito la soluzione è piccola: spesso basta spostare una riga. (Cerchi un altro errore? Vedi la panoramica degli errori del compilatore.)
Il messaggio di errore
Sezione intitolata “Il messaggio di errore”error[E0499]: cannot borrow `balance` as mutable more than once at a time --> src/main.rs:5:20 |4 | let deposit = &mut balance; | ------------ first mutable borrow occurs here5 | let withdraw = &mut balance; | ^^^^^^^^^^^^ second mutable borrow occurs here6 |7 | *deposit += 50; | -------------- first borrow later used hereIl compilatore ti dice: «non puoi prendere in prestito balance come mutabile più di una volta alla volta». Le etichette mostrano il primo prestito mutabile, il secondo e il punto in cui il primo viene ancora usato.
Ecco il programma che lo produce:
fn main() { let mut balance = 100;
let deposit = &mut balance; let withdraw = &mut balance;
*deposit += 50; *withdraw -= 30;
println!("Balance: {balance}");}Cosa significa
Sezione intitolata “Cosa significa”Il compilatore indica tre punti, e insieme raccontano tutta la storia:
- Riga 4:
depositdiventa un prestito mutabile dibalance. - Riga 5:
withdrawprova a diventare un secondo prestito mutabile dibalance. - Riga 7:
depositviene ancora usato dopo la riga 5. Quindi il primo prestito è ancora vivo quando inizia il secondo.
La terza etichetta è quella importante. Due prestiti &mut sono un problema solo quando le loro durate si sovrappongono. Se deposit avesse finito prima della riga 5, non ci sarebbe nessun errore.
Pensa a un documento condiviso con una sola chiave di «modifica». Chi ha la chiave può cambiare il documento. Rust non fa una copia della chiave: la seconda persona deve aspettare che la prima la restituisca.
Perché Rust si comporta così
Sezione intitolata “Perché Rust si comporta così”La regola del prestito (borrowing) di Rust è: in ogni momento puoi avere tanti lettori (&) oppure un solo scrittore (&mut), mai più di uno scrittore. Un riferimento &mut è una promessa: «finché esisto io, nessun altro può toccare questo valore».
Questa promessa previene bug reali. Se due pezzi di codice potessero modificare lo stesso valore nello stesso momento, uno potrebbe ridimensionare un Vec mentre l’altro punta ancora alla sua vecchia memoria. In altri linguaggi questo significa un crash o dati corrotti in silenzio. Tra thread diversi, è una data race.
Grazie a questa regola, il compilatore può essere sicuro che un &mut è l’unico modo per raggiungere i dati finché è vivo. Ottieni questa sicurezza senza nessun costo durante l’esecuzione. Il prezzo è che a volte devi spiegare più chiaramente al compilatore che i tuoi due prestiti in realtà non si scontrano.
Come risolverlo
Sezione intitolata “Come risolverlo”Soluzione 1: finisci il primo prestito prima di iniziare il secondo
Sezione intitolata “Soluzione 1: finisci il primo prestito prima di iniziare il secondo”Prova prima questa. Risolve la maggior parte degli errori E0499. Un prestito dura fino al suo ultimo uso, non fino alla fine del blocco. Quindi fai tutto il lavoro con il primo riferimento, poi crea il secondo:
fn main() { let mut balance = 100;
let deposit = &mut balance; *deposit += 50; // ultimo uso di `deposit`: il suo prestito finisce qui
let withdraw = &mut balance; // ✅ ora nessun altro lo ha in prestito *withdraw -= 30;
println!("Saldo: {balance}");}Output:
Saldo: 120Spesso le variabili in più non ti servono affatto: balance += 50; e poi balance -= 30; funzionano altrettanto bene.
Soluzione 2: prendi in prestito parti che non si sovrappongono
Sezione intitolata “Soluzione 2: prendi in prestito parti che non si sovrappongono”A volte ti servono davvero due &mut nello stesso momento, ma puntano a parti diverse dei dati. Il compilatore non sempre riesce a vederlo, quindi glielo mostri tu.
Per due elementi di un Vec o di una slice, tasks[0] e tasks[1] prendono in prestito entrambi l’intero tasks. Usa split_at_mut (in inglese) per tagliarlo in due metà che non si sovrappongono di sicuro:
fn main() { let mut tasks = vec![String::from("scrivere"), String::from("testare")];
// Dividi il Vec in due metà che non si sovrappongono let (left, right) = tasks.split_at_mut(1); let first = &mut left[0]; let second = &mut right[0];
std::mem::swap(first, second); println!("{tasks:?}");}Output:
["testare", "scrivere"](Solo per scambiare due elementi, tasks.swap(0, 1) è ancora più semplice. split_at_mut serve quando ti servono entrambi i riferimenti per un lavoro vero.)
Con una struct, lo stesso errore compare quando chiami due metodi che ricevono entrambi &mut self, come p.name_mut() e p.score_mut(). Ogni chiamata prende in prestito tutta la struct. Se invece prendi in prestito direttamente i campi, il compilatore vede che sono separati:
struct Player { name: String, score: u32,}
fn main() { let mut p = Player { name: String::from("Ferris"), score: 0 };
// Prendi in prestito i campi direttamente, non tramite metodi let name = &mut p.name; let score = &mut p.score;
name.push_str(" il Granchio"); *score += 10; println!("{} ha {} punti", p.name, p.score);}Output:
Ferris il Granchio ha 10 puntiSoluzione 3: usa gli indici invece di tenere dei riferimenti
Sezione intitolata “Soluzione 3: usa gli indici invece di tenere dei riferimenti”Un classico: cicli su un Vec con iter_mut() e intanto provi a fare push dentro il ciclo. L’iteratore tiene un &mut per tutto il ciclo, quindi push è un secondo prestito mutabile:
fn main() { let mut queue = vec![1, 2, 3]; for job in queue.iter_mut() { *job *= 10; queue.push(*job); // ❌ E0499: `queue` è già in prestito a iter_mut() }}Quando il ciclo deve modificare la collezione stessa, cicla sulle posizioni. Un indice è solo un numero, quindi non prende in prestito niente:
fn main() { let mut queue = vec![1, 2, 3];
// Cicla sulle posizioni invece di tenere un riferimento dentro `queue` for i in 0..queue.len() { queue[i] *= 10; let job = queue[i]; queue.push(job); }
println!("{queue:?}");}Output:
[10, 20, 30, 10, 20, 30]0..queue.len() viene calcolato una volta sola, prima che il ciclo parta, quindi i nuovi elementi non vengono visitati di nuovo. Ogni prestito queue[i] dura una sola riga.
Soluzione 4: RefCell (ultima risorsa)
Sezione intitolata “Soluzione 4: RefCell (ultima risorsa)”RefCell sposta il controllo dei prestiti dalla compilazione all’esecuzione. Puoi chiamare borrow_mut() senza una variabile mut, purché non ci siano mai due prestiti mutabili vivi nello stesso momento:
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100); // nota: non serve `mut`
*balance.borrow_mut() += 50; // prestito, modifica, il prestito finisce al `;` *balance.borrow_mut() -= 30;
println!("Saldo: {}", balance.borrow());}Output:
Saldo: 120Siamo sinceri: RefCell non elimina la regola, la controlla solo più tardi. Se tieni vive due guardie borrow_mut() nello stesso momento, il programma compila ma va in panic quando lo esegui:
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100);
let mut deposit = balance.borrow_mut(); let mut withdraw = balance.borrow_mut(); // 💥 `deposit` è ancora vivo
*deposit += 50; *withdraw -= 30;}thread 'main' (12345) panicked at src/main.rs:7:32:RefCell already borrowedIl messaggio del panic significa «RefCell è già in prestito». Quindi ricorri a RefCell solo quando i dati sono davvero condivisi (per esempio dentro Rc<RefCell<T>>) e le Soluzioni 1–3 non vanno bene. Non è un modo per far tacere il compilatore.
Dove lo incontri di solito
Sezione intitolata “Dove lo incontri di solito”- Memorizzare
&mutin un ciclo.handles.push(&mut counter)dentro un cicloforfallisce con «mutably borrowed here in the previous iteration of the loop» (preso in prestito come mutabile qui, nel giro precedente del ciclo). Ogni giro del ciclo aggiunge un altro&mutallo stesso valore. Soluzione: memorizza indici o valori posseduti, oppure modifica il valore direttamente nel ciclo. - Due chiamate a
get_mutsu unaHashMap.let a = stock.get_mut("apples")e poilet p = stock.get_mut("pears")prendono in prestitostockdue volte. Soluzione: finisci conaprima di chiamare di nuovoget_mut, oppure usastock.get_disjoint_mut(["apples", "pears"])per ottenerli entrambi insieme. - Due closure che modificano la stessa variabile.
let mut inc = || count += 1;elet mut inc2 = || count += 2;tengono ciascuna un&mut countfinché vivono. Soluzione: usa una sola closure, oppure chiama la prima per l’ultima volta prima di creare la seconda. Vedi Closure.
Errori correlati
Sezione intitolata “Errori correlati”- E0502: cannot borrow as mutable because it is also borrowed as immutable: la stessa idea, ma uno dei prestiti è un
&in sola lettura. - E0382: borrow of moved value: hai usato un valore dopo averlo spostato da un’altra parte.
- E0106: missing lifetime specifier: Rust deve sapere quanto vive un riferimento restituito.
- Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. Quando Rust segnala E0499?
2. Crei let a = &mut v;, usi a per l'ultima volta e poi crei let b = &mut v;. Che cosa succede?
3. Quale strumento ti permette di ottenere due riferimenti &mut a elementi diversi della stessa slice?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- E0499 — Rust error codes index (in inglese): la spiegazione ufficiale di questo errore, con un esempio minimo.
- References and Borrowing — The Rust Book (in inglese): il capitolo che introduce la regola «un solo
&mutalla volta». - Splitting Borrows — The Rustonomicon (in inglese): perché i campi di una struct si possono prendere in prestito separatamente ma gli elementi di una slice no, e come funziona
split_at_mut. - RefCell and Interior Mutability — The Rust Book (in inglese): quando ha senso spostare i controlli dei prestiti all’esecuzione.