Salta ai contenuti

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

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 use
3 | 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ù.

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:

  1. I ^^^^ sotto String::from("hi"): qui viene creata una String temporanea.
  2. Il - alla fine della riga: qui, al ;, quella String viene liberata.
  3. Il - sotto s alla riga 3: ma s (un &str che punta dentro quella String) 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.

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

L’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.

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(); // ❌ E0716

Rust 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: 2
▶ Provalo tu

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

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.

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); // ❌ E0716
let best = vec![72, 95, 88].iter().max(); // ❌ E0716

Il &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 il Vec (lo suggerisce anche rustc). A quel punto non ti serve nemmeno &mut, basta let 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)
▶ Provalo tu

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.

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.

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