Errore Rust E0382: borrow of moved value (uso di un valore spostato)
E0382 significa che hai ceduto un valore (l’hai spostato, in inglese «moved») e poi hai provato a usarlo di nuovo. È l’errore più comune nella prima settimana con Rust, e quasi sempre si risolve con una riga. Questa pagina ti mostra prima la soluzione, poi ti spiega che cosa sta succedendo. Per gli altri errori di questa serie, vedi Errori del compilatore Rust.
Il messaggio di errore
Sezione intitolata “Il messaggio di errore”error[E0382]: borrow of moved value: `name` --> src/main.rs:8:25 |6 | let name = String::from("Ferris"); | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait7 | greet(name); // name moves into greet here | ---- value moved here8 | println!("Goodbye, {name}!"); // ❌ name is gone | ^^^^ value borrowed here after move |note: consider changing this parameter type in function `greet` to borrow instead if owning the value isn't necessary --> src/main.rs:1:16 |1 | fn greet(name: String) { | ----- ^^^^^^ this parameter takes ownership of the valuehelp: consider cloning the value if the performance cost is acceptable |7 | greet(name.clone()); // name moves into greet here | ++++++++Il compilatore ti dice: «prestito di un valore spostato: name». Ti suggerisce anche due strade: fare in modo che greet prenda il valore in prestito invece di possederlo, oppure clonarlo.
Ecco il programma che lo produce:
fn greet(name: String) { println!("Hello, {name}!");}
fn main() { let name = String::from("Ferris"); greet(name); // name moves into greet here println!("Goodbye, {name}!"); // ❌ name is gone}(I commenti dicono: qui name viene spostato dentro greet; alla riga dopo, name non c’è più.)
Potresti vedere anche la formulazione «use of moved value» (uso di un valore spostato). È lo stesso errore. Rust dice «borrow» quando la riga successiva legge soltanto il valore (come println!) e «use» quando prova a spostarlo di nuovo (come let other = name;).
Cosa significa
Sezione intitolata “Cosa significa”Leggi le tre etichette dall’alto in basso. Raccontano tutta la storia:
- Riga 6:
nameè unaString, eStringnon èCopy. Quindi passarla da qualche parte la sposta. - Riga 7:
greet(name)sposta la stringa dentro la funzione. Ora ègreeta possederla, e la stringa viene distrutta quandogreetfinisce. - Riga 8: provi a leggere di nuovo
name, mamainnon la possiede più.
Pensa a quando dai l’unica chiave di casa a un amico. Una volta che gliel’hai data, non puoi aprire la porta con quella chiave, anche se ti ricordi benissimo di averla avuta.
Perché Rust si comporta così
Sezione intitolata “Perché Rust si comporta così”In Rust ogni valore ha esattamente un proprietario (è l’ownership, il possesso). Quando il proprietario se ne va, il valore viene ripulito. È così che Rust libera la memoria senza un garbage collector. Le regole sono spiegate nella pagina Ownership.
Se due variabili possedessero la stessa String, alla fine entrambe proverebbero a liberare la stessa memoria. È un «double free» (doppia liberazione), un bug reale che fa crashare i programmi in C e C++. Rust lo impedisce facendo in modo che l’assegnazione e le chiamate di funzione spostino il valore, così c’è sempre un solo proprietario.
I tipi piccoli e semplici come i32, bool e char sono Copy: passarli crea una copia poco costosa, e con loro non vedrai mai E0382. I tipi che possiedono memoria sullo heap, come String, Vec e la maggior parte delle struct, invece vengono spostati.
Come risolverlo
Sezione intitolata “Come risolverlo”Soluzione 1: prendi in prestito con &
Sezione intitolata “Soluzione 1: prendi in prestito con &”Sceglila quando la funzione deve solo guardare il valore. Nella maggior parte dei casi è la soluzione giusta. Trasforma il parametro in un riferimento e passa &name:
fn greet(name: &str) { println!("Ciao, {name}!");}
fn main() { let name = String::from("Ferris"); greet(&name); // lo presti, non lo cedi println!("Arrivederci, {name}!"); // ✅ è ancora nostro}Output:
Ciao, Ferris!Arrivederci, Ferris!Funzionerebbe anche &String, ma di solito si sceglie &str perché accetta anche i letterali stringa. Vedi Prestito (borrowing) e Stringhe. Se la funzione deve modificare il valore, usa invece &mut.
Soluzione 2: clonalo
Sezione intitolata “Soluzione 2: clonalo”Sceglila quando l’altro codice ha davvero bisogno di una sua copia, per esempio per memorizzarla in una struct o in un Vec, e a te serve ancora l’originale. .clone() crea una copia completa e indipendente:
struct Player { name: String, score: u32,}
fn main() { let name = String::from("Ferris"); let player = Player { name: name.clone(), score: 0 }; println!("{} parte con {} punti", player.name, player.score); println!("Nome originale: {name}");}Output:
Ferris parte con 0 puntiNome originale: FerrisSiamo sinceri: clonare una stringa corta o un piccolo Vec costa poco, e va benissimo, soprattutto mentre stai imparando. Preoccupatene solo quando cloni grandi quantità di dati dentro un ciclo che gira tantissime volte. Se un prestito (Soluzione 1) funziona, preferisci quello.
Soluzione 3: deriva Copy per i tipi piccoli
Sezione intitolata “Soluzione 3: deriva Copy per i tipi piccoli”Sceglila per struct piccole fatte solo di valori semplici, come coordinate, colori o ID. Aggiungi #[derive(Clone, Copy)] e la struct verrà copiata invece che spostata:
#[derive(Debug, Clone, Copy)]struct Point { x: i32, y: i32,}
fn show(p: Point) { println!("({}, {})", p.x, p.y);}
fn main() { let start = Point { x: 0, y: 3 }; show(start); // copia start show(start); // ✅ funziona di nuovo println!("{:?}", start);}Output:
(0, 3)(0, 3)Point { x: 0, y: 3 }Copy funziona solo se anche ogni campo è Copy. Una struct con un campo String o Vec non può essere Copy: per quelle usa Clone e chiama .clone(). Trovi di più in Trait comuni.
Soluzione 4: restituisci l’ownership
Sezione intitolata “Soluzione 4: restituisci l’ownership”Sceglila quando una funzione riceve un valore, lo modifica, e tu vuoi il risultato. La funzione restituisce il valore, e tu lo memorizzi di nuovo:
fn add_signature(mut letter: String) -> String { letter.push_str("\n-- Ferris"); letter // lo restituisce}
fn main() { let letter = String::from("Caro Rustacean,"); let letter = add_signature(letter); // lo riprendi println!("{letter}");}Output:
Caro Rustacean,-- FerrisFunziona bene per le funzioni in stile «builder». Per una semplice modifica sul posto, spesso fn add_signature(letter: &mut String) è più facile da leggere.
Dove lo incontri di solito
Sezione intitolata “Dove lo incontri di solito”Un ciclo for su un Vec. for task in tasks sposta l’intero vettore dentro il ciclo, quindi dopo non puoi più usare tasks:
for task in tasks { println!("Doing: {task}");}println!("{} tasks done", tasks.len()); // ❌ `tasks` moved due to this implicit call to `.into_iter()`Il commento riporta la nota del compilatore: tasks è stato spostato dalla chiamata implicita a .into_iter(). Soluzione: fai il ciclo su un prestito, for task in &tasks. Vedi For e Iteratori.
Mettere un valore in un Vec o in una struct. names.push(name) e Order { customer } spostano entrambi il valore all’interno. Usare name o customer dopo non funziona. Soluzione: rileggi il valore dalla sua nuova casa (names[0], order.customer), fai il resto del lavoro prima dello spostamento, oppure fai push di un .clone().
Spostare dentro un ciclo. Se log.push(message) sta dentro un ciclo, il primo giro sposta message e al secondo giro non è rimasto niente. Il compilatore dice value moved here, in previous iteration of loop (valore spostato qui, nel giro precedente del ciclo). Soluzione: fai push di message.clone(), oppure crea il valore dentro il ciclo, così ogni giro ne ha uno nuovo.
Thread e closure move. thread::spawn(move || ...) sposta ogni variabile catturata nel nuovo thread. Soluzione: clona i dati prima di creare il thread, oppure condividili con Arc. Vedi Concorrenza e Stato condiviso.
Errori correlati
Sezione intitolata “Errori correlati”- E0502: hai preso in prestito un valore e poi hai provato a modificarlo mentre il prestito era ancora in uso.
- E0499: due prestiti mutabili dello stesso valore nello stesso momento.
- E0106: manca uno specificatore di lifetime, cosa che capita spesso quando restituisci un riferimento per evitare uno spostamento.
- E0308: tipi non corrispondenti, per esempio passare una
Stringdove è atteso un&strdopo essere passato a un prestito. - Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. Chiami save(report), dove report è una String, e poi stampi report. Perché ottieni E0382?
2. Una funzione deve solo leggere un Vec<String>. Qual è di solito la soluzione migliore?
3. Quale struct può usare #[derive(Clone, Copy)]?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- E0382 — Rust error codes index (in inglese): la spiegazione ufficiale di questo errore, con altri esempi.
- What Is Ownership? — The Rust Book (in inglese): il capitolo che spiega passo passo gli spostamenti,
CloneeCopy. - References and Borrowing — The Rust Book (in inglese): come
&ti permette di usare un valore senza spostarlo. - Ownership and moves — Rust by Example (in inglese): brevi esempi eseguibili di spostamenti totali e parziali.
- Il trait
Copy— documentazione della libreria standard (in inglese): quali tipi sonoCopy, e la differenza traCopyeClone.