Erreur Rust E0106 : missing lifetime specifier (lifetime manquant)
L’erreur E0106 veut dire : vous avez écrit une référence (&) dans le type de retour d’une fonction ou dans une struct, et Rust ne sait pas à quoi elle est empruntée. Ce n’est pas un bug dans votre logique. Le compilateur a juste besoin d’un mot de plus de votre part, ou d’un petit changement de plan. Presque tout le monde rencontre cette erreur pendant sa première semaine avec Rust, et la correction tient en général en une ligne. (Cette page fait partie de Les erreurs du compilateur Rust expliquées.)
Le message d’erreur
Section intitulée « Le message d’erreur »error[E0106]: missing lifetime specifier --> src/main.rs:1:32 |1 | fn longer(a: &str, b: &str) -> &str { | ---- ---- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`help: consider introducing a named lifetime parameter |1 | fn longer<'a>(a: &'a str, b: &'a str) -> &'a str { | ++++ ++ ++ ++En français : « il manque un spécificateur de lifetime ». L’aide explique que le type de retour contient une valeur empruntée, mais que la signature ne dit pas si elle vient de a ou de b, et elle propose d’ajouter un lifetime nommé.
Le programme qui la provoque :
fn longer(a: &str, b: &str) -> &str { if a.len() >= b.len() { a } else { b }}
fn main() { println!("{}", longer("apple", "fig"));}Ce que ça veut dire
Section intitulée « Ce que ça veut dire »Une référence est un borrowing (emprunt) : elle pointe vers des données qui appartiennent à quelqu’un d’autre. Chaque fois qu’une fonction renvoie une référence, Rust doit savoir à qui sont les données vers lesquelles elle pointe, pour vérifier qu’elles restent en vie tant que l’appelant utilise le résultat.
Regardez les trois marques sous la ligne 1. Le compilateur souligne a et b avec ----, et le type de retour avec ^. Il vous dit : « vous renvoyez un &str emprunté, et il pourrait venir de a ou de b. Lequel ? » Il ne devinera pas à votre place.
Pensez à un livre de bibliothèque. Si un ami vous tend un livre en disant « il est emprunté », vous voulez savoir à qui, pour savoir quand le rendre. Un lifetime (durée de vie), c’est cette étiquette collée sur l’emprunt.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »Rust garantit qu’une référence ne peut jamais vivre plus longtemps que les données vers lesquelles elle pointe (pas de « pointeurs pendants »). Pour le vérifier lors d’un appel de fonction, le compilateur ne regarde que la signature de la fonction, pas son corps. C’est donc la signature elle-même qui doit dire d’où vient la référence renvoyée.
La plupart du temps, vous n’écrivez jamais de lifetimes, parce que Rust les complète tout seul grâce à trois règles d’élision simples :
- Chaque paramètre de type référence reçoit son propre lifetime.
- S’il y a exactement un paramètre de type référence, la référence renvoyée emprunte à celui-ci.
- S’il s’agit d’une méthode avec
&selfou&mut self, la référence renvoyée emprunte àself.
E0106 apparaît quand aucune de ces règles ne donne de réponse : deux paramètres de type référence ou plus (et pas de self), aucun paramètre de type référence, ou une référence stockée dans une struct. La leçon Les lifetimes explique tout ça en détail, et Le borrowing présente les références elles-mêmes.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : ajouter un lifetime nommé ('a)
Section intitulée « Solution 1 : ajouter un lifetime nommé ('a) »Choisissez cette solution quand la fonction renvoie vraiment un morceau de l’une de ses entrées. 'a n’est qu’un nom. L’écrire sur les entrées et sur la sortie revient à dire « le résultat vit aussi longtemps que ces entrées » :
// 'a : le &str renvoyé emprunte à a ou à b, il vit donc aussi longtemps que les deux.fn longer<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b }}
fn main() { let first = String::from("pomme"); let second = String::from("kiwi"); println!("Le mot le plus long : {}", longer(&first, &second));}Sortie :
Le mot le plus long : pommeSi le résultat vient toujours d’un seul paramètre, mettez 'a sur ce paramètre uniquement : fn lookup<'a>(map: &'a HashMap<String, String>, key: &str) -> Option<&'a str>. La suggestion du compilateur met 'a partout : ça fonctionne, mais c’est plus strict que nécessaire.
Solution 2 : renvoyer une String possédée
Section intitulée « Solution 2 : renvoyer une String possédée »Choisissez cette solution quand la fonction fabrique un nouveau texte, ou quand vous ne voulez simplement pas que l’appelant reste lié à un emprunt. Une String possédée appartient à l’appelant, il n’y a donc rien à suivre :
fn greeting(name: &str, lang: &str) -> String { if lang == "fr" { format!("Bonjour, {name} !") } else { format!("Hello, {name}!") }}
fn main() { println!("{}", greeting("Léa", "fr")); println!("{}", greeting("Sam", "en"));}Sortie :
Bonjour, Léa !Hello, Sam!Ça coûte une petite allocation mémoire. Pour la plupart des programmes, c’est tout à fait acceptable. Pas de mauvaise conscience à avoir.
Solution 3 : stocker des données possédées dans votre struct
Section intitulée « Solution 3 : stocker des données possédées dans votre struct »Un champ de struct comme title: &str provoque la même erreur :
error[E0106]: missing lifetime specifier --> src/main.rs:2:12 |2 | title: &str, | ^ expected named lifetime parameterIci, le compilateur attend un lifetime nommé pour la référence du champ title.
Pour les structs, la meilleure correction consiste presque toujours à posséder les données : utilisez String au lieu de &str, et Vec<T> au lieu de &[T]. La struct peut alors vivre aussi longtemps que vous le voulez :
#[derive(Debug)]struct Book { title: String, // possédée, pas &str pages: u32,}
fn main() { let book = Book { title: String::from("Dune"), pages: 412 }; println!("{} ({} pages)", book.title, book.pages);}Sortie :
Dune (412 pages)Ne donnez un lifetime à une struct que lorsqu’elle est une vue de courte durée sur des données qui vivent ailleurs, comme un parseur qui lit un grand texte. Vous écrivez alors <'a> sur la struct et sur le champ :
struct Excerpt<'a> { text: &'a str, // emprunte au roman ci-dessous}
fn main() { let novel = String::from("Appelez-moi Ismaël. Il y a quelques années..."); let first = novel.split('.').next().unwrap(); let excerpt = Excerpt { text: first }; println!("Extrait : {}", excerpt.text);}Sortie :
Extrait : Appelez-moi IsmaëlSolution 4 : utiliser 'static pour les littéraux de chaîne
Section intitulée « Solution 4 : utiliser 'static pour les littéraux de chaîne »Quand une fonction n’a aucun paramètre de type référence, l’erreur dit qu’il n’y a aucune valeur à laquelle emprunter (« there is no value for it to be borrowed from ») :
error[E0106]: missing lifetime specifier --> src/main.rs:1:22 |1 | fn default_name() -> &str { | ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed fromSi vous ne renvoyez que des littéraux de chaîne comme "OK", écrivez &'static str. Les littéraux sont inscrits directement dans votre programme, ils vivent donc pendant toute son exécution :
fn status_text(code: u16) -> &'static str { match code { 200 => "OK", 404 => "Not Found", _ => "Unknown", }}
fn main() { println!("200 signifie {}", status_text(200)); println!("404 signifie {}", status_text(404));}Sortie :
200 signifie OK404 signifie Not Found(« OK » et « Not Found » sont les textes officiels des codes HTTP, c’est pourquoi ils restent en anglais.)
N’utilisez 'static que pour de vrais littéraux et des constantes. Si le texte est construit pendant l’exécution (avec format!, ou lu dans un fichier), 'static ne vous aidera pas : renvoyez une String (solution 2).
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »- Les fonctions utilitaires à deux entrées, comme
fn lookup(map: &HashMap<String, String>, key: &str) -> Option<&str>. Correction : liez la sortie àmapseulement avec'a(solution 1). - Les structs qui contiennent du texte, comme un
User { name: &str }construit à partir d’une saisie analysée. Correction : utilisez des champsString(solution 3). - Les fonctions qui renvoient des messages fixes, comme
fn error_label() -> &str. Correction :&'static str(solution 4).
Bonne nouvelle pour les méthodes : fn name(&self) -> &str n’a besoin d’aucun lifetime, grâce à la règle 3.
Erreurs liées
Section intitulée « Erreurs liées »- E0382 : borrow of moved value : une autre erreur d’ownership (possession), quand on utilise une valeur après l’avoir cédée.
- E0502 : emprunt mutable alors qu’un emprunt immuable existe : le borrow checker au travail, une fois vos lifetimes en place.
- E0499 : deux emprunts mutables : deux
&mutvers les mêmes données en même temps. - E0308 : mismatched types : par exemple, renvoyer une
Stringdepuis une fonction qui annonce&str. E0515: cannot return reference to local variable: vous avez essayé la solution 1 sur des données créées dans la fonction. Renvoyez plutôt uneStringpossédée (solution 2).- Toutes les erreurs du compilateur expliquées
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Pourquoi fn first_word(s: &str) -> &str compile-t-il sans aucun lifetime ?
2. Votre struct Config { path: &str } provoque E0106. Quelle est en général la meilleure correction ?
3. Une fonction construit un message avec format! et le renvoie. Quel type de retour doit-elle avoir ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0106 — Rust error codes index (en anglais) : l’explication officielle de cette erreur.
- Validating References with Lifetimes — The Rust Book (en anglais) : toute l’histoire des lifetimes, avec l’exemple
longestet les structs qui contiennent des références. - Lifetime elision — The Rust Reference (en anglais) : les règles exactes que Rust utilise pour compléter les lifetimes à votre place.
- Elision — Rust by Example (en anglais) : de courts exemples qui montrent quand on peut omettre les lifetimes.
- Lifetimes — Rust by Example (en anglais) : des exemples exécutables de lifetimes explicites dans des fonctions, des méthodes et des structs.