Salta ai contenuti

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.

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 here
4 | v.push(4); // ❌ changes the Vec while `first` is alive
| ^^^^^^^^^ mutable borrow occurs here
5 | println!("First: {first}"); // ...because `first` is used here
| ----- immutable borrow later used here

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

Il compilatore indica tre righe, e ti servono tutte e tre per capire l’errore:

  1. Riga 3: &v[0] crea un riferimento in sola lettura dentro v. È il prestito immutabile.
  2. Riga 4: v.push(4) deve modificare v, quindi prende un prestito mutabile.
  3. Riga 5: first viene 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.

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.

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: 1
Ora: [1, 2, 3, 4]
▶ Provalo tu

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: deploy
Coda: ["deploy", "test", "release"]
▶ Provalo tu

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

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.

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

Il Vec in più costa un po’ di memoria, ma il codice è chiaro e ovviamente corretto. Di solito è un buon compromesso.

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

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.

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.

  • 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

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