Erreur Rust E0515 : cannot return reference to local variable
E0515 veut dire : votre fonction essaie de renvoyer une référence (&) vers quelque chose qu’elle a créé elle-même. Tout ce qu’une fonction crée est nettoyé quand elle se termine, la référence pointerait donc vers du vide. Presque tous les débutants en Rust tombent sur celle-ci, et la correction consiste en général à retirer un seul & pour renvoyer la valeur elle-même. (Cette page fait partie de Les erreurs du compilateur Rust expliquées.)
Le message d’erreur
Section intitulée « Le message d’erreur »error[E0515]: cannot return reference to local variable `greeting` --> src/main.rs:3:5 |3 | &greeting // ❌ greeting est libérée quand la fonction se termine | ^^^^^^^^^ returns a reference to data owned by the current functionEn français : « impossible de renvoyer une référence vers la variable locale greeting », parce que cela « renvoie une référence vers des données qui appartiennent à la fonction en cours ».
Voici le programme qui la provoque :
fn make_greeting(name: &str) -> &str { let greeting = format!("Hello, {name}!"); // une nouvelle String, qui appartient à cette fonction &greeting // ❌ greeting est libérée quand la fonction se termine}
fn main() { let text = make_greeting("Ferris"); println!("{text}");}Vous verrez plusieurs formulations de la même erreur. Elles veulent toutes dire la même chose :
cannot return reference to local variablequand vous écrivez&greeting.cannot return value referencing local variablequand vous appelez une méthode qui emprunte, commegreeting.as_str()ou&scores[2..].cannot return value referencing temporary valuequand les données n’ont jamais été rangées dans une variable, commetext.to_uppercase().as_str().
Chaque version porte l’étiquette « returns a value referencing data owned by the current function » (renvoie une valeur qui fait référence à des données appartenant à la fonction en cours).
Ce que ça veut dire
Section intitulée « Ce que ça veut dire »greeting est une variable locale : elle est créée dans make_greeting et lui appartient. Quand la fonction se termine, toutes ses variables locales sont libérées. Le &str que vous renvoyez pointerait dans une String qui n’existe plus.
C’est comme quitter votre appartement et donner la clé à un ami. La clé existe bien, mais l’appartement est vide, ou quelqu’un d’autre y habite déjà. Votre ami a besoin des meubles, pas de la clé.
Un chemin classique pour arriver ici : vous aviez d’abord écrit greeting (une String) dans une fonction qui renvoie &str, vous avez obtenu E0308 (mismatched types), et le compilateur vous a suggéré « consider borrowing here: &greeting ». Cette suggestion corrige le type, mais mène tout droit à E0515. La vraie correction, c’est de changer le type de retour.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »Une référence ne doit jamais vivre plus longtemps que la valeur vers laquelle elle pointe. Renvoyer une référence vers une variable locale créerait une référence pendante (dangling reference), un pointeur vers de la mémoire libérée. En C et en C++, ça compile, puis ça plante (ou pire, ça renvoie discrètement n’importe quoi). Rust le détecte dès la compilation.
Une référence renvoyée doit emprunter à quelque chose qui vit en dehors de la fonction : un paramètre transmis par l’appelant, un champ de self, ou une valeur 'static comme un littéral de chaîne. Les variables locales de la fonction ne conviennent pas. C’est pour ça que les annotations de lifetime ne peuvent pas corriger E0515 : 'a ne fait que décrire combien de temps vivent les données, il ne peut pas faire vivre une variable locale plus longtemps. Voir L’ownership (possession) et Le borrowing (emprunt).
Comment corriger
Section intitulée « Comment corriger »Solution 1 : renvoyer la valeur possédée
Section intitulée « Solution 1 : renvoyer la valeur possédée »Choisissez cette solution quand la fonction crée les données. C’est la bonne réponse dans la plupart des cas. Renvoyez String au lieu de &str (ou Vec<T> au lieu de &[T]) et retirez le &. La valeur est déplacée vers l’appelant, qui en devient le propriétaire :
fn make_greeting(name: &str) -> String { let greeting = format!("Bonjour, {name} !"); greeting // ✅ on donne toute la String à l'appelant}
fn top_scores() -> Vec<u32> { let mut scores = vec![42, 97, 63, 88]; scores.sort(); scores.split_off(2) // les deux meilleurs, dans un nouveau Vec}
fn main() { let text = make_greeting("Ferris"); println!("{text}"); println!("{:?}", top_scores());}Sortie :
Bonjour, Ferris ![88, 97]Ne vous inquiétez pas du coût : renvoyer une String ou un Vec ne déplace qu’un petit pointeur, une longueur et une capacité. Le texte lui-même n’est pas copié. Vous pourriez aussi écrire simplement format!("Bonjour, {name} !") comme dernière ligne.
Solution 2 : prendre les données en paramètre et renvoyer une référence vers elles
Section intitulée « Solution 2 : prendre les données en paramètre et renvoyer une référence vers elles »Choisissez cette solution quand l’appelant possède déjà les données et que vous voulez seulement pointer vers une partie de celles-ci. Si la fonction travaille sur un paramètre emprunté, la référence renvoyée emprunte à ce paramètre, qui vit en dehors de la fonction :
fn top_two(scores: &mut [u32]) -> &[u32] { scores.sort(); &scores[scores.len() - 2..] // ✅ pointe dans les données de l'appelant}
fn main() { let mut scores = vec![42, 97, 63, 88]; // appartient à main let best = top_two(&mut scores); println!("{best:?}");}Sortie :
[88, 97]Avec un seul paramètre de type référence, Rust complète le lifetime pour vous. Avec deux ou plus, vous aurez peut-être besoin de 'a (voir E0106). Ça ne marche que si vous renvoyez un morceau de l’entrée. Une valeur toute neuve construite à partir de l’entrée (to_uppercase(), format!, trim().to_string()) est de nouveau une variable locale : revenez alors à la solution 1. Voir Les slices.
Solution 3 : renvoyer &'static str pour les littéraux de chaîne
Section intitulée « Solution 3 : renvoyer &'static str pour les littéraux de chaîne »Choisissez cette solution quand tous les résultats possibles sont des textes fixes écrits dans votre code. Les littéraux de chaîne sont stockés dans le programme lui-même et vivent pour toujours, renvoyer une référence vers l’un d’eux est donc toujours sûr :
fn status_text(code: u16) -> &'static str { match code { 200 => "OK", 404 => "Not Found", 500 => "Internal Server Error", _ => "Inconnu", }}
fn main() { for code in [200, 404, 418] { println!("{code} : {}", status_text(code)); }}Sortie :
200 : OK404 : Not Found418 : Inconnu(« OK », « Not Found » et « Internal Server Error » sont les textes officiels des codes HTTP, ils restent donc en anglais.)
N’essayez pas de « réparer » un résultat de format! en écrivant &'static str dans la signature. Vous obtiendrez simplement E0515 à nouveau, parce que la String reste locale.
Solution 4 : utiliser Cow quand vous empruntez parfois et créez parfois
Section intitulée « Solution 4 : utiliser Cow quand vous empruntez parfois et créez parfois »Choisissez cette solution quand la fonction renvoie le plus souvent son entrée telle quelle, mais doit parfois construire une nouvelle chaîne. Cow (clone-on-write, copie à l’écriture) contient soit un &str emprunté, soit une String possédée : vous n’allouez de la mémoire que lorsque c’est nécessaire :
use std::borrow::Cow;
fn safe_file_name(name: &str) -> Cow<'_, str> { if name.contains(' ') { Cow::Owned(name.replace(' ', "_")) // nouvelle String seulement si nécessaire } else { Cow::Borrowed(name) // aucune copie }}
fn main() { for name in ["rapport.txt", "mes notes.txt"] { let clean = safe_file_name(name); let kind = match clean { Cow::Borrowed(_) => "empruntée", Cow::Owned(_) => "possédée", }; println!("{clean} ({kind})"); }}Sortie :
rapport.txt (empruntée)mes_notes.txt (possédée)Cow est une optimisation. Si vous n’êtes pas sûr d’en avoir besoin, renvoyez une String (solution 1). Plus de détails dans Weak et Cow.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »Renvoyer s.as_str() d’une String locale. greeting.as_str() emprunte greeting : ça échoue donc de la même façon que &greeting, avec la formulation « cannot return value referencing local variable ». Correction : renvoyez greeting et faites du type de retour une String.
Un .map() qui crée de nouvelles chaînes. Ça a l’air inoffensif, mais chaque to_lowercase() fabrique une String temporaire, libérée juste après .as_str() :
fn tags(input: &str) -> Vec<&str> { input.split(',').map(|t| t.trim().to_lowercase().as_str()).collect() // ❌ cannot return value referencing temporary value}Correction : renvoyez Vec<String> et retirez .as_str() : .map(|t| t.trim().to_lowercase()).collect(). (Sans to_lowercase(), t.trim() est une slice de input, et Vec<&str> fonctionnerait. Voir Les itérateurs.)
Une méthode qui formate un champ. fn full_name(&self) -> &str { &format!("{} {}", self.first, self.last) } donne « cannot return reference to temporary value ». Correction : -> String. Renvoyer &self.first ne pose aucun problème, parce que ces données vivent dans la struct, pas dans la méthode. Voir Les structs.
Une fonction sans paramètre de type référence. fn make_greeting() -> &String échoue plus tôt, avec E0106, parce qu’il n’y a rien à quoi elle pourrait emprunter. La correction est la même : renvoyez une String.
Erreurs liées
Section intitulée « Erreurs liées »- E0106 : missing lifetime specifier : apparaît souvent en premier ; ajouter
'ane corrigera pas une référence vers une variable locale. - E0597 : borrowed value does not live long enough : le même problème à l’intérieur d’une seule fonction, avec un bloc ou une boucle au lieu d’un
return. - E0308 : mismatched types : renvoyer une
Stringlà où&strest déclaré. Changez le type de retour, n’ajoutez pas de&. - E0382 : borrow of moved value : une fois passé aux valeurs possédées, attention à ne pas utiliser une valeur après l’avoir déplacée.
E0716: temporary value dropped while borrowed: une référence vers une valeur temporaire, utilisée plus loin dans la même fonction.- Toutes les erreurs du compilateur expliquées
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Une fonction construit un message avec format! et renvoie &message. Quelle est en général la bonne correction ?
2. Quelle fonction compile ?
3. Quand Cow<str> est-il un bon type de retour ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0515 — Rust error codes index (en anglais) : l’explication officielle de cette erreur.
- Dangling References — The Rust Book (en anglais) : l’exemple
dangle, et pourquoi la réponse est de renvoyer directement laString. - Validating References with Lifetimes — The Rust Book (en anglais) : pourquoi une référence renvoyée doit être liée à l’un des paramètres.
- The Slice Type — The Rust Book (en anglais) : renvoyer des slices
&strqui empruntent à l’entrée, la version sûre de la solution 2. Cow— documentation de la bibliothèque standard (en anglais) : le type clone-on-write de la solution 4, avec des exemples.