# Les Lifetimes Rust Expliqués : Annotations, Élision et Questions d'Entretien 2026 > Maîtriser les lifetimes Rust : annotations explicites, règles d'élision et préparation aux entretiens techniques avec des exemples pratiques. - Published: 2026-08-27 - Updated: 2026-08-27 - Author: Anthony Fillion-Maillet - Reading time: 5 min --- Les lifetimes en Rust constituent le mécanisme du compilateur pour suivre la durée de validité des références. Contrairement aux langages avec ramasse-miettes où la gestion mémoire intervient à l'exécution, le borrow checker de Rust valide la validité des références à la compilation, éliminant des catégories entières de bugs mémoire avant même que le code ne s'exécute. > **Réponse Rapide pour Entretien** > > Un lifetime en Rust est une construction à la compilation qui décrit la portée pendant laquelle une référence reste valide. Le borrow checker utilise les lifetimes pour garantir que les références ne survivent jamais aux données qu'elles pointent, prévenant les pointeurs pendants sans surcoût à l'exécution. ## Ce que les Lifetimes Représentent Réellement en Mémoire Les lifetimes ne concernent pas la durée d'existence des valeurs. Ils décrivent combien de temps les *références* à ces valeurs restent valides. Chaque référence en Rust possède un lifetime, même lorsque les annotations sont omises. Considérons cette fonction que le compilateur rejette : ```rust // dangling_reference.rs fn create_dangling() -> &String { let s = String::from("hello"); &s // ERREUR: `s` est libéré à la fin de la fonction } ``` La String `s` existe uniquement dans la portée de la fonction. Retourner une référence vers elle créerait un pointeur pendant, puisque la mémoire de la String est désallouée au retour de la fonction. Le borrow checker détecte cela à la compilation. La solution requiert soit de retourner une valeur possédée, soit de garantir que les données référencées survivent à l'appel de fonction : ```rust // valid_return.rs // Option 1: Retourner une valeur possédée fn create_owned() -> String { String::from("hello") } // Option 2: Référencer des données qui survivent à la fonction fn first_word(s: &str) -> &str { s.split_whitespace().next().unwrap_or("") } ``` Dans `first_word`, le `&str` retourné emprunte à l'entrée `s`, donc il reste valide tant que `s` l'est. L'appelant contrôle le lifetime de l'entrée. ## Syntaxe et Sémantique des Annotations de Lifetime Les annotations de lifetime explicites utilisent la syntaxe `'a`, `'b`, et ainsi de suite. Ce ne sont pas des instructions au compilateur sur la durée de vie des références. Elles décrivent les relations entre les lifetimes de plusieurs références. La [Référence Rust](https://doc.rust-lang.org/reference/trait-bounds.html#lifetime-bounds) définit les annotations de lifetime comme des paramètres génériques qui contraignent combien de temps les références doivent rester valides les unes par rapport aux autres. ```rust // lifetime_annotations.rs // Les deux entrées et la sortie partagent le même lifetime 'a fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("long string"); let result; { let string2 = String::from("short"); result = longest(&string1, &string2); println!("Le plus long: {}", result); // Valide: les deux strings existent } // println!("{}", result); // ERREUR: string2 libérée } ``` L'annotation `'a` indique au compilateur : la référence retournée sera valide pour l'*intersection* des lifetimes de `x` et `y`. Puisque `string2` a un lifetime plus court, `result` ne peut pas être utilisé après la libération de `string2`. Des lifetimes distincts multiples expriment des relations plus complexes : ```rust // multiple_lifetimes.rs // La sortie est liée uniquement au lifetime du premier paramètre fn first_only<'a, 'b>(x: &'a str, _y: &'b str) -> &'a str { x } fn main() { let owned = String::from("owned"); let result; { let temporary = String::from("temporary"); result = first_only(&owned, &temporary); } // result toujours valide: dépend seulement de owned println!("{}", result); } ``` ## Règles d'Élision des Lifetimes en Rust 2024 Le compilateur applique trois règles d'élision pour inférer les lifetimes lorsque les annotations sont omises. Ces règles, documentées dans le [Rustonomicon](https://doc.rust-lang.org/nomicon/lifetime-elision.html), réduisent le code répétitif sans sacrifier la sécurité. > **Les Trois Règles d'Élision** > > 1. Chaque référence d'entrée obtient son propre paramètre de lifetime > 2. S'il existe exactement un lifetime d'entrée, il s'applique à toutes les références de sortie > 3. Si `&self` ou `&mut self` existe, son lifetime s'applique à toutes les références de sortie Ces règles gèrent la plupart des patterns courants : ```rust // elision_examples.rs // Règle 1: Chaque entrée obtient son propre lifetime fn takes_two(x: &str, y: &str) {} // Le compilateur lit: fn takes_two<'a, 'b>(x: &'a str, y: &'b str) {} // Règle 2: Un seul lifetime d'entrée se propage à la sortie fn first_char(s: &str) -> &str { &s[0..1] } // Le compilateur lit: fn first_char<'a>(s: &'a str) -> &'a str // Règle 3: Le lifetime de &self se propage à la sortie impl Parser { fn peek(&self) -> &Token { &self.tokens[self.position] } // Le compilateur lit: fn peek<'a>(&'a self) -> &'a Token } ``` Quand l'élision échoue, le compilateur exige des annotations explicites. Cela arrive le plus souvent avec des fonctions retournant des références dérivées de plusieurs entrées : ```rust // elision_fails.rs // ERREUR: Impossible de déterminer le lifetime de sortie fn ambiguous(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } } // CORRECTION: L'annotation explicite résout l'ambiguïté fn unambiguous<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } ``` ## Lifetimes des Structs et la Relation Outlives Les structs contenant des références nécessitent des annotations de lifetime pour exprimer que les données empruntées doivent survivre à la struct : ```rust // struct_lifetimes.rs struct Excerpt<'a> { text: &'a str, } impl<'a> Excerpt<'a> { // new emprunte à l'entrée, donc Excerpt ne peut pas survivre à la source fn new(source: &'a str, start: usize, end: usize) -> Self { Excerpt { text: &source[start..end] } } // level() retourne des données possédées, pas de lifetime dans la signature fn level(&self) -> u32 { self.text.len() as u32 / 10 } } fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let excerpt = Excerpt::new(&novel, 0, 16); println!("Extrait: {}", excerpt.text); } // novel libérée, mais excerpt déjà hors de portée ``` Le `'a` dans `Excerpt<'a>` lie la validité de la struct au lifetime du `text` emprunté. Tenter d'utiliser un `Excerpt` après que sa String source soit libérée déclenche une erreur de compilation. Pour les structs avec plusieurs références, chacune peut avoir des lifetimes distincts : ```rust // multiple_struct_lifetimes.rs struct Comparison<'a, 'b> { baseline: &'a str, candidate: &'b str, } impl<'a, 'b> Comparison<'a, 'b> { fn new(baseline: &'a str, candidate: &'b str) -> Self { Comparison { baseline, candidate } } fn baseline_only(&self) -> &'a str { self.baseline } } ``` ## Le Lifetime 'static et Quand l'Utiliser Le lifetime `'static` indique des données qui vivent pendant toute l'exécution du programme. Les littéraux de chaîne ont ce lifetime car ils sont intégrés dans le binaire : ```rust // static_lifetime.rs let s: &'static str = "Je vis éternellement"; // Les constantes sont implicitement 'static const CONFIG_VERSION: &str = "2.0.0"; // Les globales thread-safe requièrent 'static static COUNTER: AtomicU64 = AtomicU64::new(0); ``` Un pattern courant implique les bornes `T: 'static`, ce qui confond souvent les développeurs. Cette borne signifie que `T` ne contient aucune référence non-statique, pas que `T` doit être une référence : ```rust // static_bound.rs use std::thread; fn spawn_task(data: T) { thread::spawn(move || { // data déplacée dans le thread, doit vivre indépendamment println!("Traitement dans le thread"); }); } fn main() { let owned = String::from("données possédées"); spawn_task(owned); // OK: String est 'static (possède ses données) let reference = "empruntée"; // spawn_task(reference); // OK: &'static str } ``` Le thread pourrait survivre à la fonction qui l'a créé, donc les données déplacées dans les threads ne doivent pas contenir de références vers des variables locales de la pile. ## Erreurs de Lifetime Courantes et Leurs Solutions Plusieurs patterns font régulièrement trébucher les développeurs. Comprendre ceux-ci accélère le débogage. **Retourner des références vers des variables locales :** ```rust // error_local_ref.rs fn bad_split(text: &str, delimiter: char) -> (&str, &str) { let parts: Vec<&str> = text.split(delimiter).collect(); (parts[0], parts[1]) // OK: parts[i] emprunte à text, pas au Vec } fn truly_bad() -> &str { let local = String::from("local"); &local // ERREUR: local libérée à la fin de la fonction } ``` **Stocker des références dans des structs avec des lifetimes incompatibles :** ```rust // error_struct_lifetime.rs struct Cache { data: String, // view: &str, // ERREUR: nécessite un paramètre de lifetime } // Les structs auto-référentielles requièrent unsafe ou des crates comme ouroboros struct CacheWithView<'a> { data: String, view: Option<&'a str>, // Ne peut pas pointer vers self.data en sécurité } ``` Les structs auto-référentielles, où un champ référence un autre champ, nécessitent soit `Pin` avec du code unsafe, soit des crates comme [ouroboros](https://docs.rs/ouroboros/). Le module [Rust smart pointers](/technologies/rust/interview-questions/smart-pointers) couvre ces patterns avancés. **Bornes de lifetime sur les implémentations de traits :** ```rust // trait_lifetime_bounds.rs trait Processor { fn process<'a>(&self, input: &'a str) -> &'a str; } struct Prefixer { prefix: String, } impl Processor for Prefixer { // Impossible de retourner &format!(...) - serait temporaire fn process<'a>(&self, input: &'a str) -> &'a str { input // Doit retourner input ou une partie de celui-ci } } ``` ## Higher-Ranked Trait Bounds (HRTBs) pour les Lifetimes Génériques Les higher-ranked trait bounds utilisent la syntaxe `for<'a>` pour exprimer qu'un type doit satisfaire un trait pour *n'importe quel* lifetime, pas seulement un spécifique : ```rust // hrtb.rs use std::fmt::Debug; // F doit être appelable avec n'importe quel lifetime 'a fn apply_to_refs(f: F) where F: for<'a> Fn(&'a str) -> &'a str, { let owned = String::from("test"); let result = f(&owned); println!("{}", result); } fn identity(s: &str) -> &str { s } fn main() { apply_to_refs(identity); } ``` Les HRTBs apparaissent fréquemment dans les APIs acceptant des closures et l'écosystème [Rust async/await](/blog/rust/rust-async-await-tokio-futures-concurrency) où les futures doivent fonctionner avec des références de lifetimes variés. ## Questions d'Entretien sur les Lifetimes Rust Les entretiens techniques sondent la compréhension des lifetimes à plusieurs niveaux de profondeur. Ces questions apparaissent régulièrement dans les postes Rust. **Question : Pourquoi ce code échoue-t-il à compiler ?** ```rust // interview_q1.rs fn get_str() -> &str { "hello" } ``` **Réponse :** Le type de retour nécessite une annotation de lifetime explicite. Bien que le littéral de chaîne ait un lifetime `'static`, la signature de fonction ne l'exprime pas. La correction : `fn get_str() -> &'static str`. **Question : Expliquer pourquoi ceci compile et si c'est sûr :** ```rust // interview_q2.rs fn longest<'a>(x: &'a str, _y: &str) -> &'a str { x } ``` **Réponse :** Ceci compile parce que le lifetime de sortie est lié uniquement à `x`. Le paramètre `_y` peut avoir n'importe quel lifetime puisque la valeur de retour n'en dépend pas. C'est sûr : la validité de la référence retournée dépend uniquement du lifetime de `x`. **Question : Que se passe-t-il quand on essaie de stocker une référence à côté de données possédées ?** ```rust // interview_q3.rs struct Config<'a> { name: String, description: &'a str, } ``` **Réponse :** Ce pattern est valide mais contraint l'utilisation de `Config`. La struct ne peut pas survivre à ce que `description` référence. Pour des données possédées qui doivent se référencer elles-mêmes, envisager d'utiliser `String` pour les deux champs, ou les techniques couvertes dans [Rust ownership et borrowing](/blog/rust/rust-ownership-borrowing-demystified). **Question : Comment les lifetimes interagissent-ils avec les trait objects ?** ```rust // interview_q4.rs trait Formatter { fn format(&self, input: &str) -> String; } fn get_formatter<'a>() -> Box { // ... } ``` **Réponse :** Les trait objects ont une borne de lifetime `'static` implicite par défaut. Écrire `Box` permet explicitement au trait object de contenir des références avec le lifetime `'a`. Sans la borne explicite, `Box` équivaut à `Box`. ## Variance des Lifetimes : Covariance et Contravariance La variance détermine comment les lifetimes se rapportent quand les types sont imbriqués. Les références Rust suivent ces règles : - `&'a T` est **covariante** en `'a` : un lifetime plus long peut substituer un plus court - `&'a mut T` est **invariante** en `T` : le type doit correspondre exactement - `fn(&'a T)` est **contravariante** en `'a` : un lifetime plus court peut substituer un plus long ```rust // variance.rs fn covariant_example() { let s: &'static str = "static"; let r: &str = s; // OK: 'static vit plus longtemps que n'importe quel 'a } fn invariant_example() { let mut vec: Vec<&'static str> = vec!["a"]; // let s = String::from("local"); // vec.push(&s); // ERREUR: &s n'est pas &'static str } ``` Comprendre la variance importe lors de la conception d'APIs génériques qui acceptent ou retournent des références. L'article [Rust traits et generics](/blog/rust/rust-traits-generics-advanced-guide) couvre les patterns génériques avancés. ## Annotations de Lifetime en Pratique : Points Clés - Les lifetimes décrivent la validité des références, pas l'existence des valeurs. Le borrow checker les utilise pour prévenir les pointeurs pendants à la compilation. - Les règles d'élision gèrent la plupart des cas automatiquement. Les annotations explicites deviennent nécessaires lors du retour de références dérivées de plusieurs entrées. - Les lifetimes de struct expriment la relation outlives : toute struct contenant des références doit être paramétrée par les lifetimes de ces références. - La borne `'static` sur les génériques signifie "ne contient pas de références non-statiques", pas "doit être une référence". - Les structs auto-référentielles nécessitent une gestion spéciale via `Pin`, du code unsafe, ou des crates d'aide. - Les questions d'entretien se concentrent sur la compréhension de pourquoi le code échoue à compiler et comment les annotations de lifetime changent les garanties de validité. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/rust/rust-lifetimes-explained-annotations-elision-interview