Durées de vie non-lexicales en Rust : ce que c'est et comment cela simplifie le code

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Que sont les durées de vie non-lexicales ? Découverte de l'amélioration majeure de Rust

\n\n

Toute personne ayant écrit en Rust ne serait-ce que quelques semaines a déjà rencontré cette situation : le code est parfaitement logique, sûr, mais le compilateur (ou plus précisément, son strict borrow checker) refuse de le compiler. Vous regardez l'erreur et pensez : « Mais je n'utilise plus cette référence ! Pourquoi se plaint-il ? ».

\n\n

Avant la version Rust 1.31 (édition 2018), la réponse était simple : « Parce que la durée de vie d'une variable (lifetime) est déterminée par sa portée — lexicalement ». Ce sont les durées de vie lexicales. Depuis Rust 2018, le compilateur est devenu plus intelligent. Il a acquis les durées de vie non-lexicales (NLL).

\n\n

Dans cet article, nous allons comprendre ce qu'est cette fonctionnalité, pourquoi son apparition a été une révolution pour les développeurs Rust, et comment elle rend le code plus propre et plus intuitif. Nous ne nous plongerons pas dans les méandres de la théorie des types, mais nous concentrerons sur des exemples pratiques.

\n\n

Le problème des durées de vie lexicales : pourquoi le borrow checker était trop strict

\n\n

Rappelons comment fonctionne l'emprunt (borrowing) en Rust. Nous avons des règles :

\n
    \n
  • À tout moment, il peut y avoir soit une seule référence mutable (&mut T), soit un nombre quelconque de références immuables (&T).
  • \n
  • Les références doivent toujours être valides (on ne peut pas référencer des données supprimées).
  • \n
\n\n

Avant NLL, le compilateur déterminait la durée de vie d'une référence par sa portée lexicale. C'est-à-dire du { au } où la variable est déclarée. Si une référence est créée à l'intérieur d'un bloc, elle « vit » jusqu'à la fin de ce bloc, même si vous ne l'utilisez plus.

\n\n

Regardez cet exemple classique qui ne se compilait pas dans les anciennes versions de Rust :

\n\n
fn main() {\n    let mut data = vec![1, 2, 3];\n\n    let x = &data[0];  // référence immuable\n\n    data.push(4);       // tentative d'emprunt mutable\n\n    println!("{}", x);  // utilisation de la référence immuable\n}
\n\n

Du point de vue humain : nous avons créé une référence x, puis modifié le vecteur, puis utilisé x. Mais le vecteur data lors de l'appel à push aurait pu réallouer la mémoire, et la référence x serait devenue invalide. Le compilateur a raison — c'est dangereux.

\n\n

Mais que se passe-t-il si nous utilisons la référence avant la modification ? Voici la vraie douleur :

\n\n
fn main() {\n    let mut name = String::from("Alice");\n\n    let first_char = &name[0..1]; // référence immuable\n    println!("First char: {}", first_char); // on l'utilise\n\n    name.push_str(" Johnson"); // on modifie la chaîne\n    println!("Full name: {}", name);\n}
\n\n

Ce code est sûr. Nous avons utilisé la référence first_char, affiché son contenu, puis modifié la chaîne. Mais avant Rust 2018, le compilateur donnait une erreur ! Pourquoi ? Parce que la durée de vie lexicale de la référence first_char durait jusqu'à la fin de la fonction main, c'est-à-dire jusqu'à l'accolade fermante. Et un emprunt mutable (push_str) à l'intérieur de cet intervalle est interdit.

\n\n

Les développeurs devaient utiliser des solutions de contournement : introduire des blocs supplémentaires {} pour limiter manuellement la durée de vie de la référence :

\n\n
fn main() {\n    let mut name = String::from("Alice");\n\n    {\n        let first_char = &name[0..1];\n        println!("First char: {}", first_char);\n    } // la référence first_char meurt ici\n\n    name.push_str(" Johnson");\n    println!("Full name: {}", name);\n}
\n\n

Cela fonctionnait, mais c'était peu pratique, encombrait le code d'accolades inutiles et rendait la lecture difficile. Surtout dans des fonctions complexes avec plusieurs emprunts.

\n\n

Durées de vie non-lexicales (NLL) : comment cela fonctionne

\n\n

L'idée des NLL est géniale dans sa simplicité : la durée de vie d'une référence n'est pas déterminée par sa portée, mais par l'endroit où elle est réellement utilisée.

Blogs

Book Recommendations