Errore Rust E0502: cannot borrow as mutable because it is also borrowed as immutable
E0502 significa che hai provato a modificare un valore mentre un’altra parte del codice lo stava ancora leggendo tramite un riferimento. Capita di continuo con i vettori, e di solito la soluzione è spostare una riga o copiare un valore. Questa pagina ti dà prima la soluzione, poi ti spiega perché Rust ci tiene tanto. Per gli altri errori di questa serie, vedi Errori del compilatore Rust.
Il messaggio di errore
Sezione intitolata “Il messaggio di errore”error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable --> src/main.rs:4:5 |3 | let first = &v[0]; // a reference into the Vec | - immutable borrow occurs here4 | v.push(4); // ❌ changes the Vec while `first` is alive | ^^^^^^^^^ mutable borrow occurs here5 | println!("First: {first}"); // ...because `first` is used here | ----- immutable borrow later used hereIl compilatore ti dice: «non puoi prendere in prestito v come mutabile, perché è già in prestito come immutabile». Le etichette indicano dove nasce il prestito immutabile, dove avviene quello mutabile e dove il primo viene usato più tardi.
Ecco il programma che lo produce:
fn main() { let mut v = vec![1, 2, 3]; let first = &v[0]; // a reference into the Vec v.push(4); // ❌ changes the Vec while `first` is alive println!("First: {first}"); // ...because `first` is used here}(I commenti dicono: un riferimento dentro il Vec; modifica il Vec mentre first è ancora vivo; perché first viene usato qui.)
Cosa significa
Sezione intitolata “Cosa significa”Il compilatore indica tre righe, e ti servono tutte e tre per capire l’errore:
- Riga 3:
&v[0]crea un riferimento in sola lettura dentrov. È il prestito immutabile. - Riga 4:
v.push(4)deve modificarev, quindi prende un prestito mutabile. - Riga 5:
firstviene usato di nuovo. È questa la riga che tiene vivo il primo prestito anche durante la riga 4.
Senza la riga 5, il codice compilerebbe. Il problema non è che esistano entrambi i prestiti: è che si sovrappongono.
Perché Rust si comporta così
Sezione intitolata “Perché Rust si comporta così”La regola è: tanti lettori oppure un solo scrittore, mai entrambi nello stesso momento. L’hai imparata nella pagina Prestito (borrowing).
Per un Vec c’è un motivo molto concreto. Un Vec tiene i suoi elementi in un unico blocco di memoria. Quando fai push e quel blocco è pieno, il Vec sposta tutto in un blocco nuovo e più grande e libera quello vecchio. Qualsiasi riferimento al vecchio blocco (come first) ora punta a memoria liberata. In C++ è il famoso bug dell’«invalidazione degli iteratori», che può far crashare il programma o fargli leggere spazzatura senza dire niente.
È come segnarti l’indirizzo di un amico che poi si trasferisce in una casa più grande. Il tuo biglietto sembra ancora giusto, ma punta a un edificio vuoto. Rust si rifiuta di compilare il codice invece di lasciarti andare a visitare l’edificio vuoto.
Come risolverlo
Sezione intitolata “Come risolverlo”Soluzione 1: finisci di leggere prima di scrivere
Sezione intitolata “Soluzione 1: finisci di leggere prima di scrivere”Prova prima questa. Da Rust 2018, un prestito finisce al suo ultimo uso, non alla parentesi graffa di chiusura. Si chiamano non-lexical lifetimes. Quindi basta finire di usare il riferimento prima di modificare il vettore:
fn main() { let mut v = vec![1, 2, 3]; let first = &v[0]; println!("Primo: {first}"); // ultimo uso di `first`: il prestito finisce qui v.push(4); // ✅ nessuno sta più leggendo println!("Ora: {:?}", v);}Output:
Primo: 1Ora: [1, 2, 3, 4]Soluzione 2: copia il valore
Sezione intitolata “Soluzione 2: copia il valore”Sceglila quando ti serve il vecchio valore dopo la modifica. Prendi una copia invece di un riferimento. Per i numeri, basta togliere il &. Per una String, usa .clone():
fn main() { let mut queue = vec![String::from("deploy"), String::from("test")]; let next = queue[0].clone(); // una copia tutta nostra, non un riferimento queue.push(String::from("release")); println!("Il prossimo: {next}"); println!("Coda: {:?}", queue);}Output:
Il prossimo: deployCoda: ["deploy", "test", "release"]Con let first = v[0]; su un Vec<i32> ottieni una copia gratis, perché i32 è Copy.
Soluzione 3: usa iter_mut o retain per modificare gli elementi sul posto
Sezione intitolata “Soluzione 3: usa iter_mut o retain per modificare gli elementi sul posto”Sceglila quando hai scritto un ciclo come for p in prices.iter() e poi hai provato prices.remove(i) o a modificare gli elementi lì dentro. Anche questo dà E0502. La libreria standard ha metodi fatti proprio per questo:
fn main() { let mut prices = vec![120, 45, 300, 80];
// modifica ogni elemento sul posto for p in prices.iter_mut() { *p = *p * 9 / 10; // sconto del 10% }
// togli gli elementi che non vuoi prices.retain(|&p| p >= 50);
println!("{:?}", prices);}Output:
[108, 270, 72]iter_mut ti dà un &mut per ogni elemento, quindi c’è sempre un solo scrittore. retain fa la rimozione al posto tuo, in sicurezza, in un solo passaggio. Vedi Vettori e Iteratori.
Soluzione 4: raccogli le modifiche, poi applicale
Sezione intitolata “Soluzione 4: raccogli le modifiche, poi applicale”Sceglila quando devi aggiungere elementi in base a quello che leggi. Prima leggi e raccogli, poi scrivi in un passaggio separato:
fn main() { let mut scores = vec![10, 25, 7, 40]; let bonuses: Vec<i32> = scores.iter().filter(|&&s| s > 20).map(|s| s * 2).collect(); scores.extend(bonuses); // il prestito in lettura è finito, quindi possiamo scrivere println!("{:?}", scores);}Output:
[10, 25, 7, 40, 50, 80]Il Vec in più costa un po’ di memoria, ma il codice è chiaro e ovviamente corretto. Di solito è un buon compromesso.
Soluzione 5: usa gli indici
Sezione intitolata “Soluzione 5: usa gli indici”Tienila come ultima risorsa, quando le altre soluzioni non vanno bene. Un indice è solo un numero, non un prestito, quindi non blocca le modifiche:
fn main() { let mut scores = vec![10, 25, 7]; for i in 0..scores.len() { if scores[i] > 20 { scores.push(scores[i] * 2); } } println!("{:?}", scores);}Output:
[10, 25, 7, 50]0..scores.len() viene calcolato una volta sola, prima che il ciclo parta, quindi i nuovi elementi non vengono visitati. Fai attenzione quando togli elementi per indice: le posizioni si spostano e puoi saltare elementi o andare in panic. Per togliere, retain (Soluzione 3) è più sicuro.
Dove lo incontri di solito
Sezione intitolata “Dove lo incontri di solito”Fare push in un Vec mentre ci cicli sopra. for s in &scores { scores.push(..) } prende in prestito scores per tutto il ciclo. Soluzione: prima raccogli e poi fai extend (Soluzione 4), oppure cicla per indice (Soluzione 5).
Chiamare un metodo &mut self mentre cicli su un campo. Succede spessissimo dentro i blocchi impl:
fn audit(&mut self) { for item in &self.items { self.record(item); // ❌ cannot borrow `*self` as mutable because it is also borrowed as immutable }}self.record prende in prestito in modo mutabile tutto self, compreso self.items. Soluzione: usa direttamente l’altro campo (self.log.push(item.clone())), perché Rust permette di prendere in prestito due campi diversi nello stesso momento. Oppure clona prima gli elementi. Vedi Struct.
Tenere un valore di una HashMap mentre inserisci. let apples = stock.get("apples").unwrap();, poi stock.insert(..) e poi l’uso di apples danno E0502. Soluzione: copia fuori il valore con let apples = *stock.get("apples").unwrap(); oppure con .copied(). Vedi HashMap.
Errori correlati
Sezione intitolata “Errori correlati”- E0499: due prestiti mutabili nello stesso momento. La stessa idea, con due scrittori invece di un lettore e uno scrittore.
- E0382: hai spostato un valore e poi l’hai usato. Compare spesso quando correggi E0502 spostando invece di prendere in prestito.
- E0106: manca uno specificatore di lifetime in una funzione che restituisce un riferimento.
- Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. Perché let first = &v[0]; v.push(4); println!("{first}"); non compila?
2. Vuoi togliere da un Vec<i32> tutti gli elementi minori di 50. Qual è la soluzione più pulita per E0502?
3. Quando finisce un prestito immutabile nel Rust moderno?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- E0502 — Rust error codes index (in inglese): la spiegazione ufficiale di questo errore.
- Storing Lists of Values with Vectors — The Rust Book (in inglese): contiene proprio questo esempio
let first = &v[0]; v.push(..)e spiega perché fallisce. - References and Borrowing — The Rust Book (in inglese): la regola «tanti lettori oppure un solo scrittore» per intero.
- Non-lexical lifetimes — The Edition Guide (in inglese): perché ora un prestito finisce al suo ultimo uso invece che alla parentesi graffa di chiusura.
Vec— documentazione della libreria standard (in inglese):retain,iter_mut,extende gli altri metodi che ti permettono di modificare un vettore in sicurezza.