web-dev-qa-db-fra.com

Que sont les durées de vie non lexicales?

Rust a RFC lié à des durées de vie non lexicales qui a été approuvé doit être implémenté dans le langage pendant longtemps. Récemment , la prise en charge de cette fonctionnalité par Rust s’est beaucoup améliorée et est considérée comme complète.

Ma question est la suivante: en quoi consiste exactement une durée de vie non lexicale?

58
Stargateur

Il est plus facile de comprendre ce que sont les durées de vie non lexicales en comprenant ce que sont les lexical . Dans les versions de Rust avant que des vies non-lexicales soient présentes, ce code échouera:

fn main() {
    let mut scores = vec![1, 2, 3];
    let score = &scores[0];
    scores.Push(4);
}

Le compilateur Rust voit que scores est emprunté par la variable score, ce qui empêche toute mutation ultérieure de scores:

error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let score = &scores[0];
  |                  ------ immutable borrow occurs here
4 |     scores.Push(4);
  |     ^^^^^^ mutable borrow occurs here
5 | }
  | - immutable borrow ends here

Cependant, un humain peut trivialement voir que cet exemple est trop conservateur: score n'est jamais utilisé ! Le problème est que l’emprunt de scores par score est lexical - il dure jusqu’à la fin du bloc dans lequel il est contenu:

fn main() {
    let mut scores = vec![1, 2, 3]; //
    let score = &scores[0];         //
    scores.Push(4);                 //
                                    // <-- score stops borrowing here
}

Les durées de vie non lexicales corrigent cela en améliorant le compilateur pour qu'il comprenne ce niveau de détail. Le compilateur peut maintenant dire plus précisément quand un emprunt est nécessaire et ce code sera compilé.

Une chose merveilleuse à propos des durées de vie non lexicales est qu'une fois activé, personne n'y pensera jamais. Il deviendra simplement "ce que Rust fait") et les choses fonctionneront (espérons-le) simplement.

Pourquoi les durées de vie lexicales sont-elles autorisées?

Rust ne doit autoriser que la compilation de programmes connus et sûrs. Cependant, il est impossible d'autoriser exactement seulement les programmes sûrs et de rejeter les programmes dangereux. À cette fin, Rust est plutôt conservateur: certains programmes sûrs sont rejetés. Les durées de vie lexicales en sont un exemple.

Les durées de vie lexicales étaient beaucoup plus faciles à implémenter dans le compilateur, car la connaissance des blocs est "triviale", alors que la connaissance du flux de données l'est moins. Le compilateur devait être réécrit pour introduire et utiliser une "représentation intermédiaire" (MIR) . Ensuite, le vérificateur d’emprunt (a.k.a. "borrowck") a dû être réécrit pour utiliser MIR au lieu de l’arbre de syntaxe abstraite (AST). Ensuite, les règles du vérificateur d'emprunt ont dû être affinées pour être plus fines.

Les durées de vie lexicales ne gênent pas toujours le programmeur, et il existe de nombreuses façons de contourner les durées de vie lexicales, même si elles sont ennuyeuses. Dans de nombreux cas, cela impliquait l'ajout d'accolades supplémentaires ou d'une valeur booléenne. Cela a permis à Rust 1.0 d’être expédié et d’être utile pendant de nombreuses années avant que des durées de vie non lexicales ne soient mises en œuvre.

Fait intéressant, certains schémas bons ont été développés en raison de la durée de vie lexicale. Le principal exemple pour moi est le motif entry . Ce code échoue avant les durées de vie non lexicales et compile avec ce dernier:

fn example(mut map: HashMap<i32, i32>, key: i32) {
    match map.get_mut(&key) {
        Some(value) => *value += 1,
        None => {
            map.insert(key, 1);
        }
    }
}

Cependant, ce code est inefficace car il calcule le hachage de la clé deux fois. La solution créée parce que des durées de vie lexicales est plus courte et plus efficace:

fn example(mut map: HashMap<i32, i32>, key: i32) {
    *map.entry(key).or_insert(0) += 1;
}

Le nom "vies non lexicales" ne me semble pas juste

La durée de vie d'une valeur est l'intervalle de temps pendant lequel la valeur reste à une adresse mémoire spécifique (voir Pourquoi ne puis-je pas stocker une valeur et une référence à cette valeur dans la même structure? pendant une période plus longue explication). La fonctionnalité appelée durée de vie non lexicale ne ne change pas la durée de vie des valeurs, elle ne peut donc pas rendre la durée de vie non lexicale. Cela ne fait que rendre plus précis le suivi et la vérification des emprunts de ces valeurs.

Un nom plus précis pour cette caractéristique pourrait être "non-lexical empruntes". Certains développeurs de compilateur font référence au "emprunt emprunté MIR" sous-jacent.

Les durées de vie non lexicales n'ont jamais été conçues pour être une fonctionnalité "orientée utilisateur", en soi. Ils ont en grande partie grandi dans notre esprit à cause des petits bouts de papier que nous recevons de leur absence. Leur nom était principalement destiné à des fins de développement interne et sa modification à des fins de marketing n’était jamais une priorité.

Oui, mais comment je l'utilise?

Dans Rust 1.31 (publié le 2018-12-06), vous devez vous inscrire à l'édition Rust 2018 dans votre Cargo.toml:

[package]
name = "foo"
version = "0.0.1"
authors = ["An Devloper <[email protected]>"]
edition = "2018"

À partir de Rust 1.36, l'édition 2015 de Rust 2015 permet également des durées de vie non lexicales.

L'implémentation actuelle des durées de vie non lexicales est en "mode de migration". Si le vérificateur d'emprunt NLL réussit, la compilation se poursuit. Si ce n'est pas le cas, le vérificateur d'emprunt précédent est appelé. Si l'ancien vérificateur d'emprunt autorise le code, un avertissement est imprimé pour vous informer que votre code risque de se briser dans une version ultérieure de Rust et doit être mis à jour.

Dans les versions nocturnes de Rust, vous pouvez choisir la rupture forcée via un indicateur de fonctionnalité:

#![feature(nll)]

Vous pouvez même vous inscrire à la version expérimentale de NLL en utilisant le drapeau du compilateur -Z polonius.

Un échantillon de problèmes réels résolus par des durées de vie non lexicales

84
Shepmaster