Erreur Rust E0716 : temporary value dropped while borrowed (valeur temporaire)
E0716 signifie : vous avez emprunté une valeur qui n’a pas de nom de variable, puis vous avez continué à utiliser cet emprunt sur une ligne suivante. Rust jette ces valeurs sans nom à la fin de l’instruction, donc l’emprunt pointerait vers rien. Le message fait peur, mais c’est l’une des erreurs d’emprunt les plus faciles à corriger : en général, il suffit de couper une ligne en deux. (Cette page fait partie de Les erreurs du compilateur Rust expliquées.)
Le message d’erreur
Section intitulée « Le message d’erreur »error[E0716]: temporary value dropped while borrowed --> src/main.rs:2:13 |2 | let s = String::from("hi").as_str(); // s emprunte une String qui n'a pas de nom | ^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement | | | creates a temporary value which is freed while still in use3 | println!("{s}"); // ❌ cette String a déjà disparu | - 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 emprunte une String qui n'a pas de nom |Autrement dit (rustc s’exprime toujours en anglais) : une valeur temporaire a été libérée alors qu’elle était encore empruntée ; le compilateur propose de la ranger dans une variable avec let pour qu’elle vive plus longtemps. Voici le programme qui la provoque :
fn main() { let s = String::from("hi").as_str(); // s emprunte une String qui n'a pas de nom println!("{s}"); // ❌ cette String a déjà disparu}Ce que ça veut dire
Section intitulée « Ce que ça veut dire »Une valeur temporaire est une valeur créée au milieu d’une expression, mais jamais stockée dans une variable. Dans la ligne ci-dessus, String::from("hi") construit une vraie String, mais personne ne la possède : il n’y a pas de let nom = ... pour elle. Rust libère une valeur temporaire à la fin de l’instruction, c’est-à-dire au ;.
Lisez les trois annotations de gauche à droite :
- Les
^^^^sousString::from("hi"): ici, uneStringtemporaire est créée (creates a temporary value). - Le
-en fin de ligne : ici, au;, cetteStringest libérée (freed at the end of this statement). - Le
-souss, ligne 3 : maiss(un&strqui pointe dans cetteString) est encore utilisé ici (borrow later used here).
Imaginez un serveur qui note votre commande sur un post-it et le jette dès qu’il s’éloigne. Si vous dites à un ami « la commande est sur ce post-it », votre ami ne trouve rien. Vous devez garder le post-it vous-même, ou noter la commande quelque part où elle reste.
La partie help: du message est en général la réponse : créez une variable (rustc l’appelle binding) pour que la valeur ait un propriétaire qui vit plus longtemps.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »C’est la même règle que pour E0597 : une référence ne doit jamais survivre à la valeur qu’elle désigne, sinon elle devient une référence pendante vers de la mémoire libérée. La seule différence : dans E0597, la valeur a un nom et meurt à un }, alors que dans E0716, la valeur n’a pas de nom et meurt au ;.
Utiliser une valeur temporaire dans la même instruction ne pose aucun problème. println!("{}", String::from("hi").as_str()); compile, parce que l’emprunt se termine avant le ;. L’erreur n’apparaît que lorsque l’emprunt sert encore sur une ligne suivante. Voir Quand un emprunt se termine-t-il ? et Les lifetimes.
Il existe une exception bien pratique, appelée extension de la durée de vie des temporaires (temporary lifetime extension). Si vous mettez & (ou &mut) directement devant la valeur temporaire dans un let, Rust garde discrètement la temporaire en vie aussi longtemps que la variable. C’est pour ça que ces lignes compilent :
struct Label<'a> { text: &'a str,}
fn make_title() -> String { String::from("Boîte de réception")}
fn main() { let greeting = &String::from("hi"); // ✅ prolongée : & directement dans le let let queue = &mut vec![1, 2, 3]; // ✅ prolongée queue.push(4); let label = Label { text: &make_title() }; // ✅ prolongée : & directement dans un littéral de struct println!("{greeting} {queue:?} {}", label.text);}Sortie :
hi [1, 2, 3, 4] Boîte de réceptionL’extension ne traverse pas un appel de méthode ni un appel de fonction. String::from("hi").as_str(), make_title().as_str(), vec![1, 2].iter() et shout(&make_title()) empruntent tous la temporaire à l’intérieur d’un appel, donc la temporaire meurt quand même au ; et vous obtenez E0716. Inutile d’apprendre tous les cas par cœur : si vous voyez E0716, donnez un nom à la valeur.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : donner un nom à la temporaire avec let
Section intitulée « Solution 1 : donner un nom à la temporaire avec let »Choisissez cette solution presque à chaque fois. C’est ce que propose le compilateur, et c’est la bonne réponse pour la plupart des E0716. Rangez d’abord la valeur dans sa propre variable, puis empruntez cette variable à la ligne suivante.
Un cas très courant : découper une ligne saisie. read_command() renvoie une nouvelle String, .trim() et .split(' ') l’empruntent, et le Vec<&str> garde ces emprunts :
let parts: Vec<&str> = read_command().trim().split(' ').collect(); // ❌ E0716Rust pointe read_command() et indique qu’elle est libérée à la fin de l’instruction. Donnez-lui un nom :
fn read_command() -> String { // Dans un vrai programme, on lirait ici une ligne sur stdin. String::from(" ajouter lait œufs \n")}
fn main() { let line = read_command(); // la String vit maintenant jusqu'à la fin de main let parts: Vec<&str> = line.trim().split(' ').collect(); println!("{parts:?}"); println!("commande : {}, articles : {}", parts[0], parts.len() - 1);}Sortie :
["ajouter", "lait", "œufs"]commande : ajouter, articles : 2Vous tomberez exactement sur ce cas avec une vraie saisie : lines.next().unwrap().unwrap().trim().split(' ').collect() sur io::stdin().lines() donne E0716 pour la même raison. Mettez d’abord let line = lines.next().unwrap().unwrap(); sur sa propre ligne. Voir Les chaînes de caractères.
Solution 2 : garder des valeurs possédées plutôt que des références
Section intitulée « Solution 2 : garder des valeurs possédées plutôt que des références »Choisissez cette solution quand l’emprunt doit vivre plus longtemps que n’importe quelle variable que vous pourriez créer, par exemple dans une struct que vous faites circuler, ou dans un Vec que vous remplissez dans une boucle. Un let à l’intérieur d’une boucle serait libéré à la fin de chaque tour, ce qui transformerait simplement E0716 en E0597.
Voici deux versions cassées. Une struct qui contient un &str venant d’une temporaire, et une boucle qui pousse des emprunts de nouvelles chaînes :
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 solution, c’est de laisser la struct et le Vec posséder leurs chaînes. Utilisez String au lieu de &str, et supprimez le .as_str() :
struct Label { text: String, // possédé, donc pas besoin de lifetime}
fn make_title() -> String { String::from("Boîte de réception")}
fn main() { let label = Label { text: make_title() }; // la String est déplacée dans le Label println!("Étiquette : {}", label.text);
let mut names: Vec<String> = Vec::new(); for name in ["ferris", "corro"] { names.push(name.to_uppercase()); // le Vec possède chaque nouvelle String } println!("{names:?}");}Sortie :
Étiquette : Boîte de réception["FERRIS", "CORRO"]Quand on débute, les champs possédés (String, Vec<T>) sont presque toujours le choix le plus simple. Une struct avec un champ &str a besoin d’une lifetime (durée de vie) et ne peut jamais survivre aux données qu’elle emprunte. Voir Les structs qui contiennent des références et Les structs.
Solution 3 : réorganiser la chaîne d’appels
Section intitulée « Solution 3 : réorganiser la chaîne d’appels »Choisissez cette solution quand la temporaire est le début d’une chaîne d’itérateurs. Ces lignes créent un Vec avec vec![...], l’empruntent avec .iter(), et gardent l’itérateur (ou son résultat) pour plus tard :
let doubled = &mut vec![1, 2, 3].iter().map(|n| n * 10); // ❌ E0716let best = vec![72, 95, 88].iter().max(); // ❌ E0716Le &mut devant n’aide pas : l’extension de durée de vie garde l’itérateur en vie, mais pas le Vec que .iter() emprunte. Deux façons de s’en sortir :
- Utilisez
into_iter()pour que l’itérateur possède leVec(rustc le propose aussi). Vous n’avez alors plus besoin de&mutnon plus, un simplelet mutsuffit. - Terminez la chaîne par un résultat possédé, comme
.copied(),.cloned(),.sum()ou.collect::<Vec<_>>()de simples nombres, pour que plus rien n’emprunte la temporaire après le;.
fn main() { // L'itérateur possède le Vec, donc rien n'emprunte de temporaire let mut doubled = vec![1, 2, 3].into_iter().map(|n| n * 10); println!("{:?}", doubled.next()); println!("{:?}", doubled.next());
// .copied() transforme Option<&i32> en Option<i32>, une valeur sans emprunt let best = vec![72, 95, 88].iter().max().copied(); println!("Meilleur : {best:?}");}Sortie :
Some(10)Some(20)Meilleur : Some(95)Si vous avez encore besoin du Vec plus tard, revenez à la solution 1 : let scores = vec![72, 95, 88];, puis scores.iter().max(). Voir Les itérateurs.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »Verrouiller un Mutex au milieu d’une chaîne. let first = names.lock().unwrap().first(); donne E0716, parce que le verrou (le guard) est une valeur temporaire. Quand il est libéré, le verrou est relâché, donc first ne peut pas continuer à pointer à l’intérieur. Solution : let guard = names.lock().unwrap(); puis guard.first(), ou copiez les données d’un coup avec .first().cloned(). Voir L’état partagé.
Passer &temporaire à une fonction qui renvoie une référence. Avec fn shout(s: &str) -> &str, la ligne let t = shout(&make_title()); donne E0716 : le &str renvoyé emprunte la String temporaire, et l’extension de durée de vie ne traverse pas un appel de fonction. Solution : let title = make_title(); puis shout(&title).
La renvoyer depuis une fonction. Si vous écrivez make_title().as_str() comme dernière ligne d’une fonction qui renvoie une référence (comme -> &'static str), vous obtenez E0515 (« cannot return value referencing temporary value », impossible de renvoyer une valeur qui référence une temporaire) à la place. Solution : renvoyez la String.
Erreurs liées
Section intitulée « Erreurs liées »- E0597 : borrowed value does not live long enough (la valeur empruntée ne vit pas assez longtemps) : le même problème pour une valeur qui a un nom et qui est libérée à un
}. - E0515 : cannot return reference to local variable : renvoyer depuis une fonction un emprunt d’une valeur locale ou temporaire.
- E0106 : missing lifetime specifier (lifetime manquante) : un champ de struct comme
text: &stra besoin d’une lifetime. Souvent le signe qu’uneStringserait un meilleur choix. - E0502 : mutable borrow while immutably borrowed : une autre erreur du type « un emprunt est encore utilisé », quand on modifie des données au lieu de les libérer.
- E0382 : borrow of moved value (valeur déplacée) : une fois passé aux valeurs possédées, attention à ne pas utiliser une valeur après l’avoir déplacée.
- Toutes les erreurs du compilateur expliquées
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. En Rust, qu'est-ce qu'une « valeur temporaire » ?
2. let s = String::from("hi").as_str(); suivi de println!("{s}"); donne E0716. Quelle est la solution habituelle ?
3. Quelle ligne compile, grâce à l'extension de la durée de vie des temporaires ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0716 — Rust error codes index (en anglais) : l’explication officielle de cette erreur, avec la solution par
let. - References and Borrowing — The Rust Book (en anglais) : les règles d’emprunt, et pourquoi une référence ne peut jamais être pendante.
- Temporary scopes — The Rust Reference (en anglais) : les règles exactes qui disent quand une temporaire est libérée.
- Temporary lifetime extension — The Rust Reference (en anglais) : quelles formes de
letgardent une temporaire en vie, avec des exemples. - Validating References with Lifetimes — The Rust Book (en anglais) : comment Rust compare la durée de vie d’une valeur avec la durée d’utilisation d’une référence.