Salta ai contenuti

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

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 here
5 | let withdraw = &mut balance;
| ^^^^^^^^^^^^ second mutable borrow occurs here
6 |
7 | *deposit += 50;
| -------------- first borrow later used here

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

Il compilatore indica tre punti, e insieme raccontano tutta la storia:

  1. Riga 4: deposit diventa un prestito mutabile di balance.
  2. Riga 5: withdraw prova a diventare un secondo prestito mutabile di balance.
  3. Riga 7: deposit viene 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.

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.

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: 120
▶ Provalo tu

Spesso 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"]
▶ Provalo tu

(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 punti
▶ Provalo tu

Soluzione 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]
▶ Provalo tu

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.

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: 120
▶ Provalo tu

Siamo 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 borrowed

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

  • Memorizzare &mut in un ciclo. handles.push(&mut counter) dentro un ciclo for fallisce 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 &mut allo stesso valore. Soluzione: memorizza indici o valori posseduti, oppure modifica il valore direttamente nel ciclo.
  • Due chiamate a get_mut su una HashMap. let a = stock.get_mut("apples") e poi let p = stock.get_mut("pears") prendono in prestito stock due volte. Soluzione: finisci con a prima di chiamare di nuovo get_mut, oppure usa stock.get_disjoint_mut(["apples", "pears"]) per ottenerli entrambi insieme.
  • Due closure che modificano la stessa variabile. let mut inc = || count += 1; e let mut inc2 = || count += 2; tengono ciascuna un &mut count finché vivono. Soluzione: usa una sola closure, oppure chiama la prima per l’ultima volta prima di creare la seconda. Vedi Closure.

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