Aller au contenu

Erreur Rust E0597 : does not live long enough (vit trop peu longtemps)

E0597 signifie : vous avez gardé une référence (&x) plus longtemps que la valeur x elle-même. La valeur est libérée à la fin de son bloc, mais quelque chose en dehors de ce bloc veut encore utiliser la référence. C’est une erreur très courante, surtout avec les blocs internes, les boucles et les structs, et la solution consiste généralement à déplacer une ligne ou à stocker une valeur possédée plutôt qu’une référence. (Cette page fait partie de Les erreurs du compilateur Rust expliquées.)

error[E0597]: `x` does not live long enough
--> src/main.rs:5:13
|
4 | let x = 5;
| - binding `x` declared here
5 | r = &x; // r emprunte x
| ^^ borrowed value does not live long enough
6 | } // x est libérée ici
| - `x` dropped here while still borrowed
7 | println!("r: {r}"); // ❌ r pointerait vers rien
| - borrow later used here

Autrement dit (rustc s’exprime toujours en anglais) : x ne vit pas assez longtemps ; elle est libérée (dropped) alors qu’elle est encore empruntée, et l’emprunt sert plus tard. Voici le programme qui la provoque :

fn main() {
let r;
{
let x = 5;
r = &x; // r emprunte x
} // x est libérée ici
println!("r: {r}"); // ❌ r pointerait vers rien
}

Les quatre annotations racontent l’histoire dans l’ordre :

  1. Ligne 4 : x est créée à l’intérieur du bloc interne { ... } (binding x declared here).
  2. Ligne 5 : r emprunte x. Jusqu’ici, tout va bien.
  3. Ligne 6 : le bloc se termine, donc x est libérée (dropped). L’emprunt, lui, est toujours vivant.
  4. Ligne 7 : r est utilisée, mais la valeur qu’elle désigne a déjà disparu (borrow later used here, l’emprunt est utilisé plus tard ici).

Imaginez que vous notiez le numéro de chambre d’hôtel d’un ami. C’est utile tant qu’il y séjourne. Une fois qu’il a quitté l’hôtel, le numéro est toujours sur votre papier, mais il mène à une chambre vide, ou à celle d’un inconnu. Rust ne vous laisse pas garder un papier qui mène à une chambre vide.

Vous verrez aussi l’annotation courte « borrowed value does not live long enough » (la valeur empruntée ne vit pas assez longtemps). C’est la même erreur ; le titre nomme simplement la variable.

Une référence ne doit jamais survivre à la valeur qu’elle désigne. Sinon, vous auriez une référence pendante (dangling reference) : un pointeur vers de la mémoire libérée, qui contient peut-être déjà autre chose. En C et en C++, c’est une source classique de plantages et de failles de sécurité (« use after free », utilisation après libération).

En Rust, chaque valeur est libérée quand son propriétaire sort de sa portée, généralement à l’accolade fermante } du bloc où elle a été déclarée. Le borrow checker (vérificateur d’emprunts) compare deux durées : combien de temps vit la valeur et combien de temps la référence est utilisée. Si la référence est utilisée après le } de la valeur, vous obtenez E0597. Voir La portée, Le borrowing et Les lifetimes.

Remarquez ce dont le compilateur ne se plaint pas : l’emprunt de x à la ligne 5. Le problème, c’est seulement l’utilisation plus tard, à la ligne 7. Supprimez cette dernière ligne et le programme compile. C’est un bon indice pour les solutions ci-dessous : soit vous faites vivre la valeur plus longtemps, soit vous terminez l’utilisation de la référence plus tôt.

Solution 1 : déplacer la valeur dans une portée extérieure

Section intitulée « Solution 1 : déplacer la valeur dans une portée extérieure »

Choisissez cette solution quand la valeur est vraiment nécessaire aussi longtemps que la référence. C’est la solution la plus courante : déclarez la valeur avant le bloc (à l’extérieur), pour qu’elle vive au moins aussi longtemps que la référence.

fn main() {
let scores = vec![72, 95, 88]; // vit maintenant jusqu'à la fin de main
let best;
{
best = scores.iter().max().unwrap();
}
println!("Meilleur score : {best}"); // ✅ scores est toujours vivant
}

Sortie :

Meilleur score : 95
▶ Essayez par vous-même

Règle pratique : le propriétaire doit être déclaré avant celui qui emprunte, et vivre plus longtemps que lui. Si vous voyez une valeur créée dans un bloc ou une boucle, et une référence vers elle utilisée à l’extérieur, sortez la valeur.

Solution 2 : stocker des valeurs possédées plutôt que des références

Section intitulée « Solution 2 : stocker des valeurs possédées plutôt que des références »

Choisissez cette solution quand vous rassemblez des valeurs créées dans une boucle. Chaque tour de boucle crée une nouvelle String et la libère à la fin du tour, donc un Vec<&str> à l’extérieur de la boucle ne peut pas garder de références vers elles :

fn main() {
let mut names: Vec<&str> = Vec::new();
for i in 1..=3 {
let name = format!("joueur{i}");
names.push(&name); // ❌ `name` does not live long enough
}
println!("{names:?}");
}

La solution, c’est de laisser le Vec posséder les chaînes. Remplacez Vec<&str> par Vec<String> et poussez la String elle-même (elle est alors déplacée dans le vecteur, et vit donc aussi longtemps que lui) :

fn main() {
let mut names: Vec<String> = Vec::new();
for i in 1..=3 {
let name = format!("joueur{i}");
names.push(name); // déplace la String dans le Vec
}
println!("{names:?}");
}

Sortie :

["joueur1", "joueur2", "joueur3"]
▶ Essayez par vous-même

La même idée s’applique aux structs et aux HashMap : si les données sont créées dans une portée plus petite que le conteneur, stockez des String (ou des Vec<T>) plutôt que des &str (ou des &[T]). Les champs possédés sont presque toujours le bon choix quand on débute. Voir Les chaînes de caractères et Les vecteurs.

Choisissez cette solution quand vous voulez continuer à utiliser l’original, ou quand vous n’en avez qu’un emprunt. Une struct qui contient une référence (Label<'a> { text: &'a str }) ne peut pas survivre à la chaîne qu’elle désigne. Donnez à la struct un champ String possédé et stockez-y une copie :

struct Label {
text: String, // possédé, pas &str
}
fn main() {
let label;
{
let title = String::from("Boîte de réception");
label = Label { text: title.clone() };
println!("Dans le bloc : {title}");
}
println!("Étiquette : {}", label.text); // ✅ label possède sa propre copie
}

Sortie :

Dans le bloc : Boîte de réception
Étiquette : Boîte de réception
▶ Essayez par vous-même

Si vous n’avez plus besoin de title ensuite, ne clonez pas et déplacez-la : Label { text: title }. Cloner une courte chaîne ne coûte presque rien et ne pose aucun problème. Si vous n’avez qu’un &str, utilisez .to_string() pour obtenir une copie possédée.

Solution 4 : réorganiser pour que l’emprunt se termine à temps

Section intitulée « Solution 4 : réorganiser pour que l’emprunt se termine à temps »

Choisissez cette solution quand la valeur est censée vivre peu de temps. Au lieu de faire vivre la valeur plus longtemps, utilisez la référence tant que la valeur est encore vivante. Ça arrive souvent avec les fonctions qui renvoient une référence, comme le classique exemple longest :

fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
let first = String::from("Ferris le crabe");
{
let second = String::from("Corro");
let winner = longest(&first, &second);
println!("Nom le plus long : {winner}"); // ✅ utilisé avant que second soit libérée
}
}

Sortie :

Nom le plus long : Ferris le crabe
▶ Essayez par vous-même

Si winner est déclaré en dehors du bloc et affiché après, vous obtenez E0597 pour second, même si first se trouve être plus long. La signature dit que le résultat peut emprunter à l’un ou l’autre argument, donc les deux doivent lui survivre. Si vous avez vraiment besoin du résultat plus tard, stockez-le sous forme possédée : let winner = longest(&first, &second).to_string();.

Une closure stockée en dehors du bloc. Une closure (fonction anonyme) qui utilise name l’emprunte. Si la closure survit à name, vous obtenez E0597 avec une annotation en plus, value captured here (valeur capturée ici) :

let say_hello;
{
let name = String::from("Ferris");
say_hello = || println!("Bonjour, {name} !"); // ❌ `name` does not live long enough
}
say_hello();

Solution : écrivez move || ... pour que la closure prenne possession de name. Voir Les closures.

Les threads. Emprunter une variable locale dans thread::spawn(|| ...) donne une autre erreur, E0373 (« closure may outlive the current function », la closure pourrait survivre à la fonction actuelle), parce que le thread pourrait tourner plus longtemps que main. Solution : thread::spawn(move || ...), partager avec Arc, ou utiliser std::thread::scope. Voir La concurrence.

Une référence vers une valeur temporaire. let label = Label { text: String::from("Inbox").as_str() }; ressemble à E0597, mais apparaît sous la forme de E0716, « temporary value dropped while borrowed » : la String n’est jamais stockée dans une variable, donc elle est libérée à la fin de l’instruction. Solution : mettez-la d’abord dans une variable (let title = String::from("Inbox");), ou faites du champ une String.

Renvoyer une référence depuis une fonction. Si la valeur est créée dans la fonction, vous obtenez E0515 à la place. Renvoyez la valeur possédée.

Petit quiz

1. Dans let r; { let x = 5; r = &x; } suivi d'un println! de r, qu'est-ce qui déclenche vraiment E0597 ?

2. Vous construisez une String à chaque tour de boucle et poussez &name dans un Vec<&str>. Quelle est la solution habituelle ?

3. La closure d'un thread emprunte un Vec local. Quelle erreur obtenez-vous ?

Score : 0 / 3