Errore Rust E0716: temporary value dropped while borrowed (valore temporaneo)
E0716 significa: hai preso in prestito da un valore senza nome di variabile, e poi hai continuato a usare quel prestito in una riga successiva. Rust butta via questi valori senza nome alla fine dell’istruzione, quindi il prestito punterebbe al nulla. Sembra minaccioso, ma è uno degli errori di prestito più facili da risolvere: di solito basta dividere una riga in due. (Questa pagina fa parte di Errori del compilatore Rust spiegati.)
Il messaggio di errore
Sezione intitolata “Il messaggio di errore”error[E0716]: temporary value dropped while borrowed --> src/main.rs:2:13 |2 | let s = String::from("hi").as_str(); // s borrows a String that has no name | ^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement | | | creates a temporary value which is freed while still in use3 | println!("{s}"); // ❌ that String is already gone | - borrow later used here |help: consider using a `let` binding to create a longer lived value |2 ~ let binding = String::from("hi");3 ~ let s = binding.as_str(); // s borrows a String that has no name |Il compilatore dice: «valore temporaneo eliminato mentre era ancora in prestito». Questo è il programma che lo produce:
fn main() { let s = String::from("hi").as_str(); // s borrows a String that has no name println!("{s}"); // ❌ that String is already gone}I commenti dicono: s prende in prestito una String senza nome; quella String non c’è già più.
Cosa significa
Sezione intitolata “Cosa significa”Un temporaneo è un valore creato a metà di un’espressione ma mai salvato in una variabile. Nella riga qui sopra, String::from("hi") costruisce una vera String, ma nessuno la possiede: non c’è nessun let nome = ... per lei. Rust elimina (drop) un temporaneo alla fine dell’istruzione, cioè al ;.
Leggi le tre etichette da sinistra a destra:
- I
^^^^sottoString::from("hi"): qui viene creata unaStringtemporanea. - Il
-alla fine della riga: qui, al;, quellaStringviene liberata. - Il
-sottosalla riga 3: mas(un&strche punta dentro quellaString) viene ancora usato qui.
Pensa a un cameriere che scrive la tua ordinazione su un post-it e lo butta appena si allontana. Se dici a un amico «l’ordinazione è su quel foglietto», il tuo amico non trova niente. Il foglietto devi tenerlo tu, oppure scrivere l’ordinazione da qualche parte dove resta.
La parte help: del messaggio di solito è la risposta: crea una variabile (rustc la chiama binding) così il valore ha un proprietario che vive più a lungo.
Perché Rust si comporta così
Sezione intitolata “Perché Rust si comporta così”È la stessa regola di E0597: un riferimento non deve mai vivere più a lungo del valore a cui punta, altrimenti diventa un riferimento pendente verso memoria già liberata. L’unica differenza è che in E0597 il valore ha un nome e muore a una }, mentre in E0716 il valore non ha nome e muore al ;.
Usare un temporaneo dentro la stessa istruzione va benissimo. println!("{}", String::from("hi").as_str()); compila, perché il prestito finisce prima del ;. L’errore compare solo quando il prestito serve ancora in una riga successiva. Vedi Quando finisce un prestito? e Lifetime.
C’è un’eccezione utile, chiamata estensione del lifetime dei temporanei (temporary lifetime extension). Se metti & (o &mut) direttamente davanti al temporaneo in un let, Rust tiene in vita il temporaneo in silenzio, finché vive la variabile. Ecco perché queste righe compilano:
struct Label<'a> { text: &'a str,}
fn make_title() -> String { String::from("Posta")}
fn main() { let greeting = &String::from("ciao"); // ✅ esteso: & direttamente nel let let queue = &mut vec![1, 2, 3]; // ✅ esteso queue.push(4); let label = Label { text: &make_title() }; // ✅ esteso: & direttamente nel letterale della struct println!("{greeting} {queue:?} {}", label.text);}Output:
ciao [1, 2, 3, 4] PostaL’estensione non attraversa una chiamata di metodo o di funzione. String::from("hi").as_str(), make_title().as_str(), vec![1, 2].iter() e shout(&make_title()) prendono tutti in prestito il temporaneo dentro una chiamata, quindi il temporaneo muore comunque al ; e ottieni E0716. Non cercare di imparare a memoria ogni caso: se vedi E0716, dai un nome al valore.
Come risolverlo
Sezione intitolata “Come risolverlo”Soluzione 1: Dai un nome al temporaneo con let
Sezione intitolata “Soluzione 1: Dai un nome al temporaneo con let”Sceglila quasi sempre. È quello che suggerisce il compilatore, ed è la risposta giusta per la maggior parte degli errori E0716. Salva prima il valore in una variabile tutta sua, poi prendi in prestito da quella variabile nella riga successiva.
Un caso molto comune è dividere una riga di input. read_command() restituisce una nuova String, .trim() e .split(' ') prendono in prestito da lei, e il Vec<&str> conserva quei prestiti:
let parts: Vec<&str> = read_command().trim().split(' ').collect(); // ❌ E0716Rust indica read_command() e dice che viene liberata alla fine dell’istruzione. Dalle un nome:
fn read_command() -> String { // In un programma vero questa riga verrebbe letta da stdin. String::from(" aggiungi latte uova \n")}
fn main() { let line = read_command(); // ora la String vive fino alla fine di main let parts: Vec<&str> = line.trim().split(' ').collect(); println!("{parts:?}"); println!("comando: {}, elementi: {}", parts[0], parts.len() - 1);}Output:
["aggiungi", "latte", "uova"]comando: aggiungi, elementi: 2Ti succederà proprio questo anche con l’input vero: lines.next().unwrap().unwrap().trim().split(' ').collect() su io::stdin().lines() dà E0716 per lo stesso motivo. Metti prima let line = lines.next().unwrap().unwrap(); su una riga a parte. Vedi Stringhe.
Soluzione 2: Tieni valori posseduti invece di riferimenti
Sezione intitolata “Soluzione 2: Tieni valori posseduti invece di riferimenti”Scegli questa quando il prestito deve vivere più a lungo di qualsiasi variabile tu possa creare, per esempio in una struct che passi in giro, o in un Vec che riempi dentro un ciclo. Un let dentro un ciclo verrebbe eliminato alla fine di ogni giro, e così trasformeresti E0716 in E0597.
Ecco due versioni che non compilano. Una struct che tiene un &str preso da un temporaneo, e un ciclo che inserisce prestiti di stringhe nuove:
let label = Label { text: make_title().as_str() }; // ❌ E0716
let mut names: Vec<&str> = Vec::new();for name in ["ferris", "corro"] { names.push(name.to_uppercase().as_str()); // ❌ E0716}La soluzione è lasciare che la struct e il Vec possiedano le loro stringhe. Usa String invece di &str e togli .as_str():
struct Label { text: String, // posseduta, quindi niente lifetime}
fn make_title() -> String { String::from("Posta in arrivo")}
fn main() { let label = Label { text: make_title() }; // la String viene spostata nella Label println!("Etichetta: {}", label.text);
let mut names: Vec<String> = Vec::new(); for name in ["ferris", "corro"] { names.push(name.to_uppercase()); // il Vec possiede ogni nuova String } println!("{names:?}");}Output:
Etichetta: Posta in arrivo["FERRIS", "CORRO"]Per chi comincia, i campi posseduti (String, Vec<T>) sono quasi sempre la scelta più semplice. Una struct con un campo &str ha bisogno di un lifetime e non può mai vivere più a lungo dei dati che prende in prestito. Vedi Struct che contengono riferimenti e Struct.
Soluzione 3: Riorganizza la catena
Sezione intitolata “Soluzione 3: Riorganizza la catena”Scegli questa quando il temporaneo è l’inizio di una catena di iteratori. Queste righe creano un Vec con vec![...], lo prendono in prestito con .iter() e tengono l’iteratore (o il suo risultato) per dopo:
let doubled = &mut vec![1, 2, 3].iter().map(|n| n * 10); // ❌ E0716let best = vec![72, 95, 88].iter().max(); // ❌ E0716Il &mut davanti non aiuta: l’estensione del lifetime tiene in vita l’iteratore, ma non il Vec da cui .iter() prende in prestito. Due vie d’uscita:
- Usa
into_iter(), così l’iteratore possiede ilVec(lo suggerisce anche rustc). A quel punto non ti serve nemmeno&mut, bastalet mut. - Chiudi la catena con un risultato posseduto, come
.copied(),.cloned(),.sum()o.collect::<Vec<_>>()di semplici numeri, così dopo il;niente prende in prestito il temporaneo.
fn main() { // L'iteratore possiede il Vec, quindi nessuno prende in prestito un temporaneo let mut doubled = vec![1, 2, 3].into_iter().map(|n| n * 10); println!("{:?}", doubled.next()); println!("{:?}", doubled.next());
// .copied() trasforma Option<&i32> in Option<i32>, un valore senza prestiti let best = vec![72, 95, 88].iter().max().copied(); println!("Migliore: {best:?}");}Output:
Some(10)Some(20)Migliore: Some(95)Se il Vec ti serve di nuovo più avanti, torna alla Soluzione 1: let scores = vec![72, 95, 88]; e poi scores.iter().max(). Vedi Iteratori.
Dove lo incontri di solito
Sezione intitolata “Dove lo incontri di solito”Bloccare un Mutex a metà di una catena. let first = names.lock().unwrap().first(); dà E0716, perché la guardia del lock è un temporaneo. Quando viene eliminata, il lock viene rilasciato, quindi first non può continuare a puntare all’interno. Soluzione: let guard = names.lock().unwrap(); e poi guard.first(), oppure copia fuori i dati in un colpo solo con .first().cloned(). Vedi Stato condiviso.
Passare &temporaneo a una funzione che restituisce un riferimento. Con fn shout(s: &str) -> &str, la riga let t = shout(&make_title()); dà E0716: il &str restituito prende in prestito la String temporanea, e l’estensione del lifetime non attraversa una chiamata di funzione. Soluzione: let title = make_title(); e poi shout(&title).
Restituirlo da una funzione. Se scrivi make_title().as_str() come ultima riga di una funzione che restituisce un riferimento (come -> &'static str), ottieni invece E0515 («cannot return value referencing temporary value»). Soluzione: restituisci la String.
Errori correlati
Sezione intitolata “Errori correlati”- E0597: borrowed value does not live long enough: lo stesso problema per un valore che ha un nome e viene eliminato a una
}. - E0515: cannot return reference to local variable: restituire da una funzione un prestito di un valore locale o temporaneo.
- E0106: missing lifetime specifier: un campo di struct come
text: &strha bisogno di un lifetime. Spesso è un segnale cheStringè la scelta migliore. - E0502: mutable borrow while immutably borrowed: un altro errore del tipo «un prestito è ancora in uso», ma riguarda il cambiare i dati invece dell’eliminarli.
- E0382: borrow of moved value: dopo essere passato ai valori posseduti, fai attenzione a non usare un valore dopo averlo spostato.
- Tutti gli errori del compilatore spiegati
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. In Rust, cos'è un "temporaneo"?
2. let s = String::from("hi").as_str(); seguito da println!("{s}"); dà E0716. Di solito qual è la soluzione?
3. Quale riga compila, grazie all'estensione del lifetime dei temporanei?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- E0716 — Rust error codes index (in inglese): la spiegazione ufficiale di questo errore, con la soluzione
let. - References and Borrowing — The Rust Book (in inglese): le regole del prestito, compreso il motivo per cui i riferimenti non possono mai restare pendenti.
- Temporary scopes — The Rust Reference (in inglese): le regole esatte su quando viene eliminato un temporaneo.
- Temporary lifetime extension — The Rust Reference (in inglese): quali forme di
lettengono in vita un temporaneo, con esempi. - Validating References with Lifetimes — The Rust Book (in inglese): come Rust confronta quanto vive un valore con quanto a lungo viene usato un riferimento.