Licence CC BY

La version stable de Rust 1.27.2 est désormais disponible !

Le borrow checker, la contre-attaque

Dernière mise à jour :
Auteurs :
Catégories :
Temps de lecture estimé : 8 minutes

Rust est un langage de programmation système axé sur la sécurité, la rapidité et la concurrence.

Pour mettre à jour votre version stable, il suffit d’exécuter la commande habituelle.

$ rustup update stable

Si vous ne disposez pas de rustup, vous pouvez en obtenir une copie sur la page de téléchargement du site officiel. N’hésitez pas également à consulter la release note de la 1.27.2 sur GitHub !

Quoi de neuf ?

Les retombées du patch d’ergonomie semblent, décidément, donner du fil à retordre à l’équipe, la poussant une nouvelle fois à publier une version mineure spécialement pour le borrow checker (encore lui ! :colere2: ).

Passons tout de suite à la révision !

Patch: Transmutation de lifetime illégale

Introduction

Si je devais résumer, vulgairement, le concept:

Le concept de lifetime explique que chaque référence d’une ressource dispose d’une durée de vie, déterminée à la compilation, qui peut être raccourcie mais pas dépassée. Implicitement ou non, le compilateur a toujours recours à ce concept.

Voilà pour la définition officieuse que je pourrais en faire.

Autrement dit, un développeur a, explicitement, affaire aux lifetimes seulement lorsque le compilateur n’est pas capable de savoir quelle ressource est censée survivre. Dans le premier exemple, aucun doute, rustc appliquera son comportement par défaut sans importuner qui que ce soit.

fn main() {
    // On créé notre ressource.
    let a: String = "Hello".to_owned();
    // On conserve notre emprunt.
    let b: &str = foo(&a);
}

// `foo` emprunte `a` puis renvoie la référence.
// Ici, `a` survit à `foo`, il n'y a pas d'ambiguïté.
fn foo(a: &str) -> &str {
    a
}

Pourquoi ?

Il n’y a qu’une seule référence &str passée en paramètre et la fonction renvoie également une référence du même type. Une ressource créée dans ce contexte ne pouvant, de toute façon, survivre à l’exécution de la fonction il est logiquement impossible que la référence puisse provenir d’autre part que du paramètre soumis.

Pour le second exemple, dont la complexité sous-jacente dépasse largement l’objectif de ce billet, le comportement par défaut du compilateur est court-circuité et l’intervention du développeur est requise.

fn main() {
    let a: String = "Hello".to_owned();
    let b: String = "there!".to_owned();
    let c: &str = foo(&a, &b);
}

fn foo(a: &str, b: &str) -> &str {
    b
}
error[E0106]: missing lifetime specifier
 --> src/main.rs:7:29
  |
7 | fn foo(a: &str, b: &str) -> &str {
  |                             ^ expected 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`

Il suffit ici de préciser que b dispose d’une durée de vie supérieure à l’exécution de cette fonction.

fn main() {
    let a: String = "Hello".to_owned();
    let b: String = "there!".to_owned();
    let c: &str = foo(&a, &b);
}

fn foo<'a>(a: &str, b: &'a str) -> &'a str {
    b
}

Problème réglé. La lifetime de a a été implicitement définie par le compilateur. Voilà pour l’entrée en matière.

Maintenant, le patch !

Une durée de vie, déterminée à la compilation, peut être raccourcie mais pas dépassée.

Le bug qui nous intéresse vient casser cette règle, passant sous silence des références potentiellement invalides.

// Le problème réside dans le fait que
// `t` est assigné à une lifetime `'a`
// qui est plus courte que `'b`.
// `t` ne devant survivre à l'exécution,
// le compilateur doit renvoyer une erreur.
fn transmute_lifetime<'a, 'b, T>(t: &'a (T,)) -> &'b T {
    match (&t, ()) {
        ((t,), ()) => t,
    }
    /*
    Que l'on pourrait simplifier en:
    match &t {
        (t,) => t,
    }
    */
}

fn main() {
    let x = {
        let y = Box::new((42,));
        transmute_lifetime(&y)
    };

    println!("{}", x);
}

La 1.27.2 corrige donc ce comportement et fait planter l’analyse.

 --> src/main.rs:7:11
  |
7 |     match (&t, ()) {
  |           ^^^^^^^^
  |
note: first, the lifetime cannot outlive the lifetime 'a as defined on the function body at 6:1...
 --> src/main.rs:6:1
  |
6 | fn transmute_lifetime<'a, 'b, T>(t: &'a (T,)) -> &'b T {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  = note: ...so that the types are compatible:
          expected (&&(T,), ())
             found (&&'a (T,), ())
note: but, the lifetime must be valid for the lifetime 'b as defined on the function body at 6:1...
 --> src/main.rs:6:1
  |
6 | fn transmute_lifetime<'a, 'b, T>(t: &'a (T,)) -> &'b T {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that reference does not outlive borrowed content
 --> src/main.rs:8:23
  |
8 |         ((t,), ()) => t,
  |                       ^

À propos de la fréquence de publication des correctifs

De nombreux utilisateurs ont remarqué que la publication des nouvelles versions (et surtout des correctifs) du langage se faisait de plus en plus régulière et se sont questionnés sur la raison.

Trois points en sont ressortis:

  1. Les moyens techniques (quantité de bande passante) de l’infrastructure se sont améliorés;
  2. L’âge et l’état (relativement en déclin) du borrow checker;
  3. Une équipe Release, dédiée à la préparation des publications des nouvelles versions, a vu le jour, facilitant davantage les modifications incrémentales.

Le borrow checker ne répondant plus vraiment aux besoins actuels, et causant un nombre croissant de problèmes en terme de maintenabilité, les nombreux correctifs viennent pallier la perfectibilité de ce dernier. En parallèle, une refonte partielle du compilateur est prévue, en espérant que cela nous permette (peut-être !) de bénéficier de messages d’erreurs encore plus précis et d’un système de vérifications plus rigoureux.


Source

Le blog de l’équipe Rust

Voir aussi

Précédemment

Aucun commentaire

Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte