Retour au cours

backend / rust

Lifetimes

Leçon 61 exercice

Explication

Ce que vous allez apprendre

  • Comprendre ce qu'un lifetime documente réellement (une relation, pas une durée)
  • Savoir pourquoi la plupart des lifetimes n'ont besoin d'aucune annotation (elision)
  • Annoter un lifetime explicite quand le compilateur ne peut pas déduire la relation seul
  • Déclarer une struct qui stocke une référence, avec son paramètre de lifetime
  • Reconnaître 'static, le lifetime qui dure toute l'exécution du programme

Dans quel contexte ?

Un développeur écrit une fonction qui compare deux chaînes et retourne la plus longue des deux. Le compilateur refuse de compiler avec un message qui semble abstrait : "missing lifetime specifier". Ce message n'est pas une punition arbitraire : il signale que le compilateur ne peut pas deviner, à lui seul, si la référence retournée doit vivre aussi longtemps que le premier ou le second argument — une ambiguïté que les lifetimes explicites permettent de lever.

D'abord, il faut dissiper un malentendu fréquent sur les lifetimes

Un lifetime n'allonge ni ne raccourcit la durée de vie réelle d'aucune donnée : il ne fait que documenter, pour le compilateur, une relation entre les durées de vie de plusieurs références. fn plus_long<'a>(x: &'a str, y: &'a str) -> &'a str dit simplement : "le résultat retourné ne vivra pas plus longtemps que le plus court entre x et y".

C'est une annotation de contrat, pas une instruction d'exécution : aucun code machine n'est généré à partir d'un lifetime, contrairement à un type comme i32 qui détermine la taille réelle en mémoire.

Une fois cette nuance comprise, une bonne nouvelle s'impose : la plupart des cas sont automatiques

Le compilateur applique des règles d'élision qui déduisent automatiquement les lifetimes dans les cas les plus courants — par exemple, une fonction avec une seule référence en entrée et une référence en sortie n'a jamais besoin d'annotation explicite, comme le montre premier_mot(s: &str) -> &str.

SituationAnnotation requise ?Exemple
Une seule référence en entréeNon (élision automatique)fn f(s: &str) -> &str
Plusieurs références, relation ambiguëOuifn plus_long<'a>(x: &'a str, y: &'a str) -> &'a str
Struct qui stocke une référenceOui, toujoursstruct Extrait<'a> { contenu: &'a str }
Méthode avec &selfNon (élision liée à self)fn contenu(&self) -> &str

Prérequis

Il faut avoir assimilé le borrowing et les références (leçon précédente) : les lifetimes n'existent que pour encadrer et vérifier ce que le borrowing autorise déjà.

Il reste un cas où l'annotation devient réellement obligatoire

Dès qu'une struct contient un champ qui est une référence, elle doit déclarer un paramètre de lifetime, comme struct Extrait<'a> { contenu: &'a str }. Cela garantit qu'aucune instance de Extrait ne pourra survivre plus longtemps que la donnée qu'elle référence — le compilateur refuse toute construction qui violerait cette garantie.

Piège fréquent

Un lifetime qui semble "trop restrictif" au premier abord (le compilateur refuse d'utiliser une référence en dehors d'un certain bloc) signale presque toujours un problème réel de durée de vie, pas un bug du compilateur. Avant de chercher à contourner l'erreur, relis attentivement où chaque donnée référencée est réellement créée et détruite.

Enfin, un lifetime spécial mérite d'être connu : 'static

'static désigne une donnée qui vit pendant toute l'exécution du programme — typiquement les littéraux de chaîne ("texte") et les constantes. C'est le lifetime le plus permissif possible, mais il ne doit pas être utilisé comme solution de facilité pour "faire taire" une erreur de lifetime mal comprise.

Bonne pratique

Laisse toujours le compilateur guider ton apprentissage des lifetimes : ses messages d'erreur sont réputés parmi les plus pédagogiques de l'écosystème des langages compilés, et ils suggèrent souvent directement la correction à apporter.

Maintenant que tu maîtrises la sécurité mémoire au niveau des références, la prochaine leçon revient à la modélisation de données avec les structs et les enums, deux briques omniprésentes dans tout code Rust.

Commandes & code

Lifetimes

Les lifetimes garantissent qu'une référence ne survit jamais à la donnée qu'elle pointe.

rust
// La plupart du temps, le compilateur infere les lifetimes (elision) sans annotation.
fn premier_mot(s: &str) -> &str {
    s.split_whitespace().next().unwrap_or("")
}

// Ici, le compilateur ne peut pas deviner quelle reference d'entree est liee a la sortie :
// il faut l'annoter explicitement avec un parametre de lifetime generique 'a
fn plus_long<'a>(x: &'a str, y: &'a str) -> &'a str {
    // 'a signifie : le retour vit AU MOINS aussi longtemps que le plus court de x et y
    if x.len() > y.len() { x } else { y }
}

// Struct contenant une reference : DOIT porter un lifetime
struct Extrait<'a> {
    contenu: &'a str,
}

impl<'a> Extrait<'a> {
    fn nouveau(texte: &'a str) -> Extrait<'a> {
        Extrait { contenu: &texte[..10.min(texte.len())] }
    }

    // elision : le compilateur infere que le retour partage le lifetime de &self
    fn contenu(&self) -> &str {
        self.contenu
    }
}

fn main() {
    let s1 = String::from("longue chaine de caracteres");
    let resultat;
    {
        let s2 = String::from("courte");
        resultat = plus_long(s1.as_str(), s2.as_str());
        println!("le plus long est: {}", resultat); // OK : utilise dans le scope de s2
    }
    // println!("{}", resultat); // ERREUR si utilise ici : s2 est droppe

    let texte = String::from("Rust garantit la securite memoire sans GC");
    let extrait = Extrait::nouveau(&texte);
    println!("{}", extrait.contenu());

    // 'static : lifetime qui dure tout le programme (litteraux de chaine, constantes)
    let s: &'static str = "je vis pendant toute l'execution du programme";
    println!("{}", s);

    println!("{}", premier_mot("le premier mot de cette phrase"));
}

Résumé

  • Un lifetime 'a documente une relation entre les durées de vie des références, il n'en change aucune.
  • La plupart des lifetimes sont élidés automatiquement (règles d'elision) ; l'annotation explicite n'est requise que dans les cas ambigus.
  • Une struct qui stocke une référence doit porter un paramètre de lifetime (struct S<'a>).
  • 'static désigne une donnée qui vit pendant toute l'exécution du programme (littéraux, const).

Exercices pratiques

1 disponible
1

Mission : sauver une struct de resume de session qui ne compile pas

Objectif : Diagnostiquer une erreur de lifetime manquant sur une struct qui stocke une reference, et l'annoter correctement.

Contexte

Un collegue veut stocker un extrait du dernier message d'un utilisateur sans dupliquer la chaine complete, pour economiser de la memoire sur des millions de sessions actives. Il ecrit :

rust
struct ResumeSession {
    extrait: &str,
}

fn creer_resume(message: &str) -> ResumeSession {
    ResumeSession { extrait: &message[..10.min(message.len())] }
}

Le compilateur refuse avec 'missing lifetime specifier'. Il ne comprend pas pourquoi une simple reference dans une struct pose probleme, alors que ses fonctions avec &str en parametre compilent sans annotation ailleurs dans son code.

Résoudre l’exercice →