IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

La version stable de Rust 1.27.2 est désormais disponible !
Le borrow checker, la contre-attaque

Le , par Songbird

470PARTAGES

13  0 
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.

Code : Sélectionner tout
$ 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 !

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 ! ).

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.

Code Rust : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
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.

Code Rust : Sélectionner tout
1
2
3
4
5
6
7
8
9
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 
}
Code Rust : Sélectionner tout
1
2
3
4
5
6
7
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.

Code Rust : Sélectionner tout
1
2
3
4
5
6
7
8
9
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.

Code Rust : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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.
Code Rust : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  
 --> 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



Voir aussi

Une erreur dans cette actualité ? Signalez-nous-la !

Avatar de Pyramidev
Expert éminent https://www.developpez.com
Le 06/08/2018 à 11:30
Salut,

Concernant les tailles de Option<u16> et Option<NonZeroU16>, l'explication est la suivante :
  • Option<u16> contient 65537 valeurs possibles (None et les valeurs de Some(0) à Some(65535)), donc ne peut pas tenir sur seulement 2 octets, quelle que soit l'implémentation.
  • Option<NonZeroU16> contient 65536 valeurs possibles (None et les valeurs de Some(1) à Some(65535)), donc peut tenir sur seulement 2 octets, si on choisit la bonne implémentation.


À mon avis, sous le capot, Option<NonZeroU16> contient un entier n entre 0 et 65535. Si n == 0, alors cela correspond à None. Sinon, cela correspond à Some(n). Je n'ai pas vérifié, mais ce serait l'implémentation la plus logique.
1  0 
Avatar de Songbird
Membre expert https://www.developpez.com
Le 11/08/2018 à 21:58
Hello,

Effectivement, je n'y avais pas pensé. Merci pour l'élément de réponse !
0  0