Errore Rust E0373: closure may outlive the current function (usa move)
E0373 significa che una closure prende in prestito una variabile locale, ma potrebbe continuare a essere eseguita (o a esistere) dopo che la funzione proprietaria di quella variabile è terminata. Di solito lo incontri la prima volta che chiami thread::spawn o che restituisci una closure da una funzione. La buona notizia: il compilatore ti dice già la soluzione, e quasi sempre è una sola parola, move. Questa pagina ti mostra prima la soluzione e poi il perché. Per gli altri errori di questa serie, vedi Errori del compilatore Rust.
Il messaggio di errore
Sezione intitolata “Il messaggio di errore”error[E0373]: closure may outlive the current function, but it borrows `scores`, which is owned by the current function --> src/main.rs:5:32 |5 | let handle = thread::spawn(|| { | ^^ may outlive borrowed value `scores`6 | println!("Scores: {scores:?}"); // ❌ only borrows scores | ------ `scores` is borrowed here |note: function requires argument type to outlive `'static` --> src/main.rs:5:18 |5 | let handle = thread::spawn(|| { | __________________^6 | | println!("Scores: {scores:?}"); // ❌ only borrows scores7 | | }); | |______^help: to force the closure to take ownership of `scores` (and any other referenced variables), use the `move` keyword |5 | let handle = thread::spawn(move || { | ++++Il compilatore dice: «la closure potrebbe vivere più a lungo della funzione corrente, ma prende in prestito scores, che appartiene alla funzione corrente». Questo è il programma che lo produce:
use std::thread;
fn main() { let scores = vec![90, 85, 77]; let handle = thread::spawn(|| { println!("Scores: {scores:?}"); // ❌ only borrows scores }); handle.join().unwrap();}Con il codice async vedrai lo stesso codice con parole leggermente diverse: «async block may outlive the current function, but it borrows user_id». È lo stesso errore, con la stessa soluzione (async move).
Cosa significa
Sezione intitolata “Cosa significa”Leggi le etichette dall’alto verso il basso:
^^ may outlive borrowed valueindica la closure||. Rust non può garantire che la closure abbia finito prima chemainvada avanti.`scores` is borrowed hereindica il punto in cui la closure usascores. Visto che aprintln!basta leggerlo, la closure ha catturato un riferimento ascores, non il vettore stesso.function requires argument type to outlive 'staticspiega perché è un problema:thread::spawnaccetta solo closure che possono vivere per tutto il tempo che serve al programma.'staticvuol dire «non prende in prestito niente che possa sparire».
Immagina di prestare il tuo quaderno a un amico dicendogli «riportamelo quando hai finito», mentre stai per trasferirti in un’altra città. Se il tuo amico è lento, il quaderno non avrà più una casa in cui tornare. Rust rifiuta questo accordo e ti chiede invece di regalare il quaderno.
Perché Rust si comporta così
Sezione intitolata “Perché Rust si comporta così”Una closure cattura le variabili nel modo più economico che funziona. Se legge soltanto una variabile, la prende in prestito con &. È perfetto per le closure che vengono eseguite subito, come quelle che passi a .map() o .filter(). Trovi di più nella pagina Closure.
Un nuovo thread è diverso. Può durare un tempo qualsiasi, e main non lo aspetta a meno che tu non chiami join. Se il thread tenesse un riferimento a scores e main finisse per primo, scores verrebbe eliminato e il thread leggerebbe memoria già liberata. È un «riferimento pendente» (dangling reference), uno dei bug più comuni nel codice multithread in C e C++. Rust lo blocca in fase di compilazione.
Lo stesso succede quando restituisci una closure. Le variabili locali della funzione (e i suoi parametri) vengono eliminate quando la funzione termina. Una closure che le prende ancora in prestito punterebbe al nulla.
Come risolverlo
Sezione intitolata “Come risolverlo”Soluzione 1: Aggiungi move (la risposta abituale)
Sezione intitolata “Soluzione 1: Aggiungi move (la risposta abituale)”Sceglila quasi sempre. È esattamente ciò che suggerisce rustc. move dice alla closure di prendere l’ownership (possesso) delle variabili che usa, così viaggiano insieme a lei:
use std::thread;
fn main() { let scores = vec![90, 85, 77]; let handle = thread::spawn(move || { let total: i32 = scores.iter().sum(); println!("Punteggi: {scores:?}, totale {total}"); }); handle.join().unwrap();}Output:
Punteggi: [90, 85, 77], totale 252La stessa soluzione funziona per una funzione che restituisce una closure. Senza move, |x| x + n prende in prestito il parametro n, che sparisce appena make_adder termina. Con move, ogni closure si tiene la propria copia di n:
fn make_adder(n: i32) -> impl Fn(i32) -> i32 { move |x| x + n}
fn main() { let add_five = make_adder(5); let add_ten = make_adder(10); println!("{}", add_five(1)); println!("{}", add_ten(1));}Output:
611move non cambia il trait della closure che ottieni. Una closure move che legge soltanto i suoi dati resta Fn e si può chiamare tutte le volte che vuoi.
Soluzione 2: Clona prima, se il valore ti serve ancora
Sezione intitolata “Soluzione 2: Clona prima, se il valore ti serve ancora”Dopo move, la variabile appartiene alla closure. Se main la usa più avanti, ottieni E0382 («borrow of moved value»), e rustc suggerisce di clonare il valore prima di spostarlo. Fai una copia per la closure e tieniti l’originale:
use std::thread;
fn main() { let guests = vec![String::from("Ana"), String::from("Ben")]; let guests_for_thread = guests.clone();
let handle = thread::spawn(move || { println!("Il thread vede {} ospiti", guests_for_thread.len()); }); handle.join().unwrap();
println!("main ha ancora: {guests:?}");}Output:
Il thread vede 2 ospitimain ha ancora: ["Ana", "Ben"]Clonare una lista piccola costa poco e va benissimo. Se i dati sono grandi, o servono a molti thread, usa Arc (Soluzione 3), così condividono tutti un’unica copia.
Soluzione 3: Condividi con Arc tra più thread
Sezione intitolata “Soluzione 3: Condividi con Arc tra più thread”Scegli questa quando più thread devono leggere gli stessi dati. Arc («atomically reference counted», conteggio dei riferimenti atomico) permette a molti proprietari di condividere un solo valore. Arc::clone copia solo un puntatore e incrementa un contatore; i dati in sé non vengono copiati:
use std::sync::Arc;use std::thread;
fn main() { let prices = Arc::new(vec![4.5, 2.0, 3.5]); let mut handles = Vec::new();
for discount in [0.0, 0.1, 0.5] { let prices = Arc::clone(&prices); // un handle per ogni thread handles.push(thread::spawn(move || { let total: f64 = prices.iter().sum(); total * (1.0 - discount) })); }
for handle in handles { println!("Totale: {:.2}", handle.join().unwrap()); } println!("Prezzi: {prices:?}");}Output:
Totale: 10.00Totale: 9.00Totale: 5.00Prezzi: [4.5, 2.0, 3.5]Da solo, Arc permette di condividere in sola lettura. Se i thread devono anche cambiare i dati, avvolgili in Arc<Mutex<T>>. Vedi Concorrenza e Stato condiviso.
Soluzione 4: Prendi in prestito in sicurezza con std::thread::scope
Sezione intitolata “Soluzione 4: Prendi in prestito in sicurezza con std::thread::scope”Scegli questa quando ai thread servono i dati solo per un lavoro breve e preferisci prendere in prestito invece di spostare o clonare. I thread avviati dentro thread::scope finiscono di sicuro prima che scope ritorni, quindi prendere in prestito variabili locali è sicuro e move non serve:
use std::thread;
fn main() { let scores = vec![90, 85, 77, 64]; let (first, second) = scores.split_at(2);
let (a, b) = thread::scope(|s| { let h1 = s.spawn(|| first.iter().sum::<i32>()); let h2 = s.spawn(|| second.iter().sum::<i32>()); (h1.join().unwrap(), h2.join().unwrap()) }); // qui entrambi i thread hanno finito
println!("{a} + {b} = {}", a + b); println!("Ancora nostri: {scores:?}");}Output:
175 + 141 = 316Ancora nostri: [90, 85, 77, 64]I thread con scope sono la risposta più pulita per «dividi il lavoro, poi usa i risultati». La lezione Stato condiviso parte proprio da loro.
Dove lo incontri di solito
Sezione intitolata “Dove lo incontri di solito”Avviare thread in un ciclo. Anche un semplice numero viene preso in prestito, se la closure lo legge soltanto:
for worker in 1..=3 { handles.push(thread::spawn(|| { println!("Worker {worker} started"); // ❌ borrows `worker` }));}Soluzione: thread::spawn(move || ...). worker è un i32, quindi move lo copia semplicemente in ogni thread.
Restituire una closure in un Box. Box::new(|| greeting.clone()) dentro una funzione che restituisce Box<dyn Fn() -> String> prende in prestito la variabile locale greeting, e rustc aggiunge note: closure is returned here. Soluzione: Box::new(move || greeting.clone()). Trovi di più in Fn, FnMut e FnOnce.
Task asincroni (Tokio e simili). tokio::spawn funziona come thread::spawn: il task può durare molto oltre la fine della funzione corrente, quindi ha bisogno di dati 'static. Se scrivi tokio::spawn(async { ... }) e dentro usi una variabile locale, ottieni «async block may outlive the current function». Soluzione: tokio::spawn(async move { ... }), e clona (o fai Arc::clone di) tutto quello che ti serve ancora fuori dal task. Vedi Async/await.
Errori correlati
Sezione intitolata “Errori correlati”- E0382: borrow of moved value: compare subito dopo che hai aggiunto
movee poi usi di nuovo la variabile fuori dalla closure. Clonala prima (Soluzione 2) o usaArc(Soluzione 3). - E0597: borrowed value does not live long enough: un parente stretto. Un riferimento è ancora in uso dopo che il valore a cui punta è stato eliminato.
- E0515: cannot return reference to local variable: lo stesso problema del «sparisce quando la funzione termina», con un riferimento normale invece di una closure.
- E0106: missing lifetime specifier: compare quando provi a restituire un riferimento (o un tipo di closure) che prende in prestito qualcosa.
- Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. La closure di un thread stampa name senza usare move. Perché ottieni E0373?
2. Aggiungi move, ma main deve ancora stampare il vettore dopo aver avviato il thread. Qual è la soluzione più semplice?
3. Quale strumento permette ai thread di prendere in prestito variabili locali senza move?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- E0373 — Rust error codes index (in inglese): la spiegazione ufficiale di questo errore, con la soluzione
move. - Using Threads to Run Code Simultaneously — The Rust Book (in inglese): la sezione «Using move Closures with Threads» analizza proprio questo errore.
- Closures — The Rust Book (in inglese): come le closure catturano le variabili per riferimento, per riferimento mutabile o per valore.
movekeyword — standard library docs (in inglese): cosa famovealle catture di una closure, con brevi esempi.std::thread::scope— standard library docs (in inglese): thread con scope che possono prendere in prestito dati locali in sicurezza.