Salta ai contenuti

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.

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` trait
7 | greet(name); // name moves into greet here
| ---- value moved here
8 | 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 value
help: 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;).

Leggi le tre etichette dall’alto in basso. Raccontano tutta la storia:

  1. Riga 6: name è una String, e String non è Copy. Quindi passarla da qualche parte la sposta.
  2. Riga 7: greet(name) sposta la stringa dentro la funzione. Ora è greet a possederla, e la stringa viene distrutta quando greet finisce.
  3. Riga 8: provi a leggere di nuovo name, ma main non 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.

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.

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

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.

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 punti
Nome originale: Ferris
▶ Provalo tu

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

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

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.

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,
-- Ferris
▶ Provalo tu

Funziona bene per le funzioni in stile «builder». Per una semplice modifica sul posto, spesso fn add_signature(letter: &mut String) è più facile da leggere.

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.

  • 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 String dove è atteso un &str dopo essere passato a un prestito.
  • Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.

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