Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

  1. #1
    Responsable Rust

    Avatar de Songbird
    Homme Profil pro
    Bidouilleur
    Inscrit en
    juin 2015
    Messages
    481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 20
    Localisation : France

    Informations professionnelles :
    Activité : Bidouilleur
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : juin 2015
    Messages : 481
    Points : 4 589
    Points
    4 589
    Billets dans le blog
    8

    Par défaut La version stable de Rust 1.27.0 est désormais disponible !

    La version stable de Rust 1.27.0 est désormais disponible !
    Optimisation des instructions à l'horizon

    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 Bash : Sélectionner tout - Visualiser dans une fenêtre à part
    $ 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.0 sur GitHub !

    Attention
    Peu de temps avant la publication de la version 1.27.0, un nouveau bug, relatif au patch d’ergonomie appliqué sur l’expression du match, a été détecté. Rappelez-vous, cette modification avait déjà causé des problèmes lors de la publication de la 1.26.0 et l’équipe Rust suppose que ce bug a également été introduit à cet instant. Le cycle de publication n’a, malgré tout, pas été interrompu et un correctif (numéroté 1.27.1 ou 1.26.3, selon les vœux de la communauté) devrait être rapidement disponible pour remédier au souci.

    Quoi de neuf ?

    Deux fonctionnalités majeures, relatives au langage et dont nous reparlerons plus bas, débarquent avec cette nouvelle version. Avant de nous atteler à celles-ci, intéressons-nous à la mise à jour de mdbook qui permet désormais aux lecteurs, des livres présents dans la Rust Bookshelf, de rechercher des termes à travers toutes les pages de leur ressource en temps réel. Essayez, par exemple, d’effectuer une recherche dans le livre officiel avec le terme ownership et vous remarquerez que le livre vous souligne toutes les occurrences du terme à travers les pages.

    Enfin, toujours en rapport avec la documentation, un nouveau livre, dédié à rustc, vient s’ajouter à la bibliothèque. Il apprendra à ses lecteurs à se servir directement du compilateur, sans passer par cargo et pourra toujours être utile en tant que pense-bête pour les arguments, par exemple.

    Single Instruction, Multiple Data (SIMD)

    Penchons-nous maintenant sur l’une des deux fonctionnalités majeures, promises par la 1.27.0: l’exploitation du paradigme SIMD.

    Pour cette partie, j’ai décidé de remanier l’exemple proposé, car il me semblait un peu confus et peu explicite.
    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    26
    27
     
    use std::fmt::Write;
     
    pub fn foo(a: &[u8], b: &[u8], c: &mut [u8]) {
        let mut buf: String = String::new();
     
        // `d` représente la valeur courante lorsque nous itérons sur la slice `a`.
        // `e` représente la valeur courante lorsque nous itérons sur la slice `b`.
        // `f` représente la valeur courante lorsque nous itérons sur la slice `c`.   
     
        for ((d, e), f) in a.iter().zip(b).zip(c) {
            *f = *d + *e;
            write!(buf, "\n`d` value: {0}\n`e` value: {1}\n`f` value: {2}\n----------", d, e, f).expect("Oops");
        }
     
        println!("{}", buf.as_str());
    }
     
    // Voir ci-dessous pour imaginer la provenance des slices
     
    fn main() {
        let a: Vec<u8> = vec![1, 2, 3];
        let b: Vec<u8> = vec![4, 5, 6];
        let mut c: Vec<u8> = vec![0, 0, 0];
        foo(&a, &b, &mut c);
        println!("{:?}", &c);
    }

    Résultat:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    `d` value: 1
    `e` value: 4
    `f` value: 5
    ----------
    `d` value: 2
    `e` value: 5
    `f` value: 7
    ----------
    `d` value: 3
    `e` value: 6
    `f` value: 9
    ----------
    [5, 7, 9]
    Ici, nous traitons deux slices et ajoutons leurs nombres respectifs dans une troisième slice. La plus simple des manières d’effectuer ces traitements est déjà illustrée par le code ci-dessus. Cependant, le compilateur peut généralement faire mieux grâce à LLVM et sa capacité à auto-vectoriser des codes de ce type.

    Imaginons que nos deux slices (a et b) disposent d’une taille égale à 16 éléments. Si chaque élément représente un octet (u8), cela signifierait que chaque slice contient 128 bits de données. En utilisant le SIMD nous pourrions placer a et b dans des registres de 128 bits, fusionner les contenus en une seule instruction, puis les copier dans c, ce qui devrait être bien plus rapide.

    Bien que les versions stables de Rust ont toujours eu recours à l’auto-vectorisation, le compilateur n’est, parfois, pas capable de déceler les cas où l’optimisation peut être effectuée. Ajoutons à cela que tous les CPU ne supportent pas le paradigme SIMD, imposant à LLVM de ne pas utiliser ce dernier pour ne pas compromettre la portabilité de votre programme.

    En réponse à ces problèmes, en 1.27.0, le module std::arch donnera accès à des outils permettant de déclencher manuellement l’optimisation, pour couvrir les cas où le compilateur n’est pas capable de le faire lui-même. Il permettra également d’inclure des fonctionnalités suivant l’implémentation choisie. Par exemple:

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"),
          target_feature = "avx2"))]
    fn foo() {
        #[cfg(target_arch = "x86")]
        use std::arch::x86::_mm256_add_epi64;
        #[cfg(target_arch = "x86_64")]
        use std::arch::x86_64::_mm256_add_epi64;
     
        unsafe {
            _mm256_add_epi64(...);
        }
    }

    _mm256_add_epi64 est l’une des fonctions nécessitant l’inclusion d’une fonctionnalité particulière: AVX2. Pour plus d’information, rendez-vous sur la partie de la documentation relative à la détection du CPU hôte.
    Ici, donc, les appels de fonction sont ajoutés à la compilation suivant l’architecture (x86 ou x86_64, en l’occurrence). Ces vérifications peuvent, toutefois, être effectuées à l’exécution grâce à la macro is_x86_feature_detected!.

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    fn foo() {
        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
        {
            if is_x86_feature_detected!("avx2") {
                return unsafe { foo_avx2() };
            }
        }
     
        foo_fallback();
    }

    is_x86_feature_detected! se charge de générer le code qui vous permettra de détecter si le CPU hôte supporte AVX2 et, si c’est le cas, d’exécuter la fonction foo_avx2. Sinon, le test est court-circuité et le programme retourne à une implémentation se passant des services de la fonctionnalité. Dans un cas l’exécution bénéficiera d’une vélocité accrue, dans l’autre vous n’aurez, au moins, pas à vous soucier de la portabilité de votre code.

    Dû à l’aspect plutôt primitif du module, il se peut que l’équipe Rust prévoie d’en stabiliser un autre, modestement nommé std::simd, pour permettre l’encapsulation de toutes ces notions et ainsi fournir des outils plus abstraits. En attendant, vous pouvez toujours vous tourner vers la crate faster. Cette dernière fournit le nécessaire pour écrire du code portable tout en exploitant le paradigme SIMD avec, somme toute, un maximum d’abstraction. Voici un exemple (sans faster):

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    let lots_of_3s = (&[-123.456f32; 128][..]).iter()
        .map(|v| {
            9.0 * v.abs().sqrt().sqrt().recip().ceil().sqrt() - 4.0 - 2.0
        })
        .collect::<Vec<f32>>();

    Avec faster:

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    let lots_of_3s = (&[-123.456f32; 128][..]).simd_iter()
        .simd_map(f32s(0.0), |v| {
            f32s(9.0) * v.abs().sqrt().rsqrt().ceil().sqrt() - f32s(4.0) - f32s(2.0)
        })
        .scalar_collect();

    Les services proposés sont sensiblement les mêmes que ceux de la std, ce qui devrait faciliter l’adoption du paradigme.

    Déclaration d'un trait dans une signature (dyn Trait)

    Avant la 1.27.0, il pouvait être difficile de faire la différence entre une signature déclarant l’utilisation d’un trait, d’une autre déclarant l’utilisation d’un objet.


    Ici, Foo pourrait tout à fait être un trait comme une structure sans que nous puissions clairement faire la distinction. Désormais, lorsque nous aurons recours à la généricité, nous pourrons utiliser le mot-clé dyn. Ce dernier précèdera chaque type représentant un trait.

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    // old => new
    Box<Foo> => Box<dyn Foo>
    &Foo => &dyn Foo
    &mut Foo => &mut dyn Foo

    Voici un exemple d’utilisation:
    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    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
     
    use std::borrow::Borrow;
     
    trait Foo{
        fn bar(&self);
    }
     
    struct Baz;
     
    impl Foo for Baz {
        fn bar(&self) {
            println!("Hello there!");
        }
    }
     
     
    fn bang() -> Box<dyn Foo> {
        Box::new(Baz)
    }
     
    fn main() {
        let a: Box<dyn Foo> = bang();
        let b: &dyn Foo = a.borrow();
        b.bar();
    }

    Pour autant, l’ancienne syntaxe n’a pas été retirée et peut donc toujours être utilisée, l’équipe Rust informant que cela casserait la compatibilité descendante. Vous pouvez cependant mettre à niveau votre code grâce à rustfix qui a été conçu spécialement dans ce but.

    A l’avenir, si vous ne souhaitez plus adopter l’ancienne syntaxe, vous pouvez activer une lint pour vous signaler des patterns obsolètes: bare-trait-object. Elle n’est pas activée par défaut pour éviter le flood de warning.

    Utilisation de l'attribut #[must_use] sur les fonctions

    Précédemment utilisé sur les types (tels que Result<T, E>), l’attribut #[must_use] peut désormais être utilisé sur les signatures des fonctions et ainsi vous prévenir, lors de la compilation, lorsque la valeur renvoyée n’est pas utilisée.

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #[must_use]
    fn double(x: i32) -> i32 {
        2 * x
    }
     
    fn main() {
        double(4); // warning: unused return value of `double` which must be used
     
        let _ = double(4); // (no warning)
    }

    Les services Clone::clone, Iterator::collect ainsi que ToOwned::to_owned font d'ores et déjà usage de l’attribut pour vous prévenir d’éventuelles opérations inutiles.

    Stabilisations apportées à la bibliothèque standard

    21 nouveaux services ont été stabilisés et publiés dans cette nouvelle version. Je ne vous ferai pas l’affront de tous vous les citer, je vous invite à vous rendre directement sur la release note si vous souhaitez en savoir plus à ce sujet.

    Fonctionnalités ajoutées à Cargo

    Dans cette version, deux fonctionnalités ont été intégrées au gestionnaire de projet.

    Premièrement, il est désormais possible de redéfinir, à la volée, le répertoire de sortie (target directory) pour la prochaine compilation grâce au flag --target-dir.

    Enfin, la dernière, mais pas des moindres: un patch de "prévention" a été appliqué à Cargo pour prévenir les utilisateurs d’un défaut connu, mais qui pourrait ne pas être corrigé dans le futur (correction entravée par la compatibilité descendante). Lorsque vous créez de nouveaux exemples (ou de nouveaux tests), Cargo se chargera de chercher les nouvelles entités à chaque recompilation. Seulement, ce comportement est court-circuité dès que vous rédigez une configuration spécifique à un exemple. Dans ce cas, Cargo traitera ce dernier, mais ne lancera plus la détection automatique pour les autres.

    En réponse à cela, une Pull Request a été soumise pour prévenir l’utilisateur lorsqu’il déclare une configuration dans son manifeste. Le message d’information ressemble à ceci:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    warning: An explicit [[bin]] section is specified in Cargo.toml which currently
    disables Cargo from automatically inferring other binary targets.
    This inference behavior will change in the Rust 2018 edition and the following
    files will be included as a binary target:
    
    * /Users/eric/Proj/rust/cargo/src/bin/cli.rs
    * /Users/eric/Proj/rust/cargo/src/bin/command_prelude.rs
    De nouvelles clés ont également été ajoutées au manifest pour permettre de configurer chaque entité (e.g. [[example]], [[bin]]) explicitement et ainsi forcer Cargo à lancer la détection. Comme pour les stabilisations, je vous invite vivement à consulter la section de la release note relatives aux clés.

    Source

    Le blog de l'équipe Rust
    Avant de poster: FAQ Rust; FAQ Dart; FAQ Java; FAQ JavaFX.
    Vous souhaiteriez vous introduire au langage Rust ? C'est par ici ou ici !
    Une question à propos du langage ? N'hésitez pas à vous rendre sur le forum !


    Pour contribuer à la rubrique, vous pouvez me contacter par MP ou contacter directement la rédaction.

  2. #2
    Responsable Rust

    Avatar de Songbird
    Homme Profil pro
    Bidouilleur
    Inscrit en
    juin 2015
    Messages
    481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 20
    Localisation : France

    Informations professionnelles :
    Activité : Bidouilleur
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : juin 2015
    Messages : 481
    Points : 4 589
    Points
    4 589
    Billets dans le blog
    8

    Par défaut La version stable de Rust 1.27.1 est désormais disponible !

    La version stable de Rust 1.27.1 est désormais disponible:
    Bogue d'emprunts et faille de sécurité du côté de rustdoc corrigés

    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 bash : Sélectionner tout - Visualiser dans une fenêtre à part
    $ 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.1 sur GitHub !

    Patch : Faux positif sur les ressources empruntées

    En Rust, les notions d’emprunt (borrowing) et de transfert (ownership) sont fondamentales et régissent la gestion des ressources. Ici, foo a l’ownership sur un objet Foo.

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    fn main() {
        let foo: Foo = Foo;
    }

    Nous pourrions nous en servir par le biais d’une référence bar, à quelques exceptions près.

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #[derive(Debug)]
    struct Foo;
     
    fn main() {
        let foo: Foo = Foo;
     
        // On emprunte l'objet `foo` en lecture seule.
        let bar: &Foo = &foo;
     
        super_foo_factory(foo);
     
        println!("{:?}", bar);
    }
     
    // Ici, la fonction prend un paramètre dont la ressource
    // doit forcément être transférée.
    fn super_foo_factory(f: Foo) -> Foo {
        /* ... On traite l'objet ... */
        f
    }

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    error[E0505]: cannot move out of `foo` because it is borrowed
      --> src/main.rs:10:23
       |
    8  |     let bar: &Foo = &foo;
       |                      --- borrow of `foo` occurs here
    9  |     
    10 |     super_foo_factory(foo);
       |                       ^^^ move out of `foo` occurs here

    Je pense qu’on ne peut pas faire plus explicite. Le compilateur est capable de différencier un emprunt (en l’occurrence bar) d’un transfert (que je n’ai pas illustré ici avec une autre variable, mais qui peut aisément l’être grâce au paramètre f de la fonction super_foo_factory).

    Seulement, comme pour le dernier bug en date causé par une révision de l’expression match, le compilateur ne semblait plus différencier les deux notions.

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    fn main() {
        let a = vec!["".to_string()];
        a.iter().enumerate()
                .take_while(|(_, &t)| false)
                .collect::<Vec<_>>();
    }

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    error[E0507]: cannot move out of borrowed content
        --> src/main.rs:4:30
        |
      4 |             .take_while(|(_, &t)| false)
        |                              ^-
        |                              ||
        |                              |hint: to prevent move, use `ref t` or `ref mut t`
        |                              cannot move out of borrowed content
     
    error: aborting due to previous error

    Ici, le compilateur nous renvoie une erreur affirmant que nous tentons de transférer une ressource empruntée et nous suggère d’effectuer un emprunt, ce qui est déjà le cas.

    En réponse à cela, le problème a été corrigé dans la 1.27.1.

    Pour ceux qui se poseraient la question :

    • enumerate() crée un itérateur contenant un tuple de deux valeurs (index, valeur) par élément;
    • take_while() crée un nouvel itérateur en accumulant les éléments matchant le prédicat (i.e. tant que le prédicat renvoie true, les éléments sont accumulés);
    • collect() crée une nouvelle collection avec les éléments triés par la dernière méthode.


    Patch: Exploitation possible du système de plugin de rustdoc

    Ce 3 juillet, Red Hat a rapporté une vulnérabilité affectant le comportement de rustdoc. En effet, le système de plugin de rustdoc dispose d’un répertoire par défaut (i.e. /tmp/rustdoc/plugins) dans lequel les plugins seront chargés. Ce dernier pouvant être accédé en écriture, sans restrictions spécifiques sur la plupart des plateformes, il est possible d’y ajouter une bibliothèque dynamique pour ainsi exécuter du code arbitraire au lancement de l’outil.

    Pour tenter de corriger progressivement la faille, il a été décidé que ce chemin par défaut serait supprimé et que l’utilisateur serait dans l’obligation de fournir explicitement un chemin dans lequel charger les plugins. L’équipe Rust prévoit de supprimer intégralement la fonctionnalité pour la version 1.28.0.

    Source

    Le blog de l'équipe Rust
    Avant de poster: FAQ Rust; FAQ Dart; FAQ Java; FAQ JavaFX.
    Vous souhaiteriez vous introduire au langage Rust ? C'est par ici ou ici !
    Une question à propos du langage ? N'hésitez pas à vous rendre sur le forum !


    Pour contribuer à la rubrique, vous pouvez me contacter par MP ou contacter directement la rédaction.

  3. #3
    Responsable Rust

    Avatar de Songbird
    Homme Profil pro
    Bidouilleur
    Inscrit en
    juin 2015
    Messages
    481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 20
    Localisation : France

    Informations professionnelles :
    Activité : Bidouilleur
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : juin 2015
    Messages : 481
    Points : 4 589
    Points
    4 589
    Billets dans le blog
    8

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

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

    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.

    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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 - Visualiser dans une fenêtre à part
    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
    Avant de poster: FAQ Rust; FAQ Dart; FAQ Java; FAQ JavaFX.
    Vous souhaiteriez vous introduire au langage Rust ? C'est par ici ou ici !
    Une question à propos du langage ? N'hésitez pas à vous rendre sur le forum !


    Pour contribuer à la rubrique, vous pouvez me contacter par MP ou contacter directement la rédaction.

  4. #4
    Responsable Rust

    Avatar de Songbird
    Homme Profil pro
    Bidouilleur
    Inscrit en
    juin 2015
    Messages
    481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 20
    Localisation : France

    Informations professionnelles :
    Activité : Bidouilleur
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : juin 2015
    Messages : 481
    Points : 4 589
    Points
    4 589
    Billets dans le blog
    8

    Par défaut La version stable de Rust 1.28 est désormais disponible !

    La version stable de Rust 1.28 est désormais disponible !
    Allocateur(s) et optimisation de la mémoire

    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.

    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.28 sur GitHub !

    Quoi de neuf ?

    Les événements passés, d’une contre-attaque ( !) matée, laissent désormais place à une nouvelle version majeure de Rust s’axant principalement sur la mémoire, autant sur sa gestion directe (optimisation de certains comportements que nous verrons plus bas) qu’indirecte (manipulation d’outils/services proposés permettant d’adapter cette gestion en fonction des besoins).

    Allons-y !

    Personnalisation de l'allocateur global

    Si vous n’avez qu’une vague idée de ce à quoi, structurellement, un allocateur mémoire pourrait ressembler, en voici un résumé:

    Il existe au moins deux "types" d’allocateurs:

    1. Les allocateurs système, qui disposent d’une implémentation native de malloc, vous permettant ainsi d’effectuer les appels système désirés. C’est, en quelque sorte, l’interaction classique entre un programme et le reste de son environnement (concernant la gestion de la mémoire, tout du moins);
    2. Les allocateurs personnalisés, qui peuvent représenter tout ce que vous pourriez imaginer pour organiser le contenu de votre mémoire, son allocation, sa libération et sa (dé)fragmentation. Les allocateurs personnalisés peuvent alors tout aussi bien proposer une implémentation de malloc avec des spécifications différentes, adaptées à un besoin très spécifique, tout comme créer un ensemble d’outils génériques offrant une alternative à l’implémentation système. Pour ne citer que l’un d’entre eux : je vous présente jemalloc !


    jemalloc

    Actuellement utilisé par le projet Rust, il propose des outils dédiés à l’analyse des performances. Par exemple, cet allocateur ne dénature (ou ne supprime) pas l’implémentation de malloc mais l’améliore pour en obtenir des résultats plus intéressants.

    Intégration à Rust

    Avec la venue de la 1.28, il est désormais possible de choisir l’allocateur à la compilation. Notez que, pour la plupart des OS, l’allocateur choisi par rust est jemalloc et peut être remplacé par l’allocateur du système grâce à la structure std::alloc::System ainsi qu’à l’attribut #[global_allocator].

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    use std::alloc::System;
     
    #[global_allocator]
    static GLOBAL: System = System;
     
    fn main() {
        let mut v = Vec::new();
        // Cette allocation sera effectuée
        // par l'allocateur du système d'exploitation
        // et non jemalloc.
        v.push(1);
    }

    Pour diverses raisons, vous pourriez également avoir besoin d’utiliser un allocateur fait-maison pour combler les besoins d’une situation délicate (notamment dans le cas où votre programme est très gourmand en RAM sur le long terme, effectuant de très nombreuses petites allocations et ralentissant alors le fonctionnement de l’implémentation classique de malloc). La bibliothèque standard propose le trait GlobalAlloc pour fournir les services nécessaires à la bonne utilisation de l’allocateur et permettre son enregistrement en tant qu’allocateur global lors l’exécution.

    Amélioration de la formulation des erreurs de formatage

    Cas très spécifique pour cette révision, puisqu’elle consiste à améliorer l’intelligibilité du diagnostic fourni par le compilateur lorsque l’utilisateur soumet un nom d’argument invalide dans son modèle de formatage.

    A titre informatif, le formatage d’une chaîne de caractères en Rust par le biais d’arguments positionnels nommés (et non indexés) se déroule comme suit:

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    println!("{bar}{foo}", foo = ", world!", bar = "Hello")

    foo et bar ne sont pas initialisés au préalable; l’ordre d’initialisation n’est pas important.
    Cependant, les identificateurs utilisés pour le formatage ne peuvent pas être préfixés par un underscore (_) et le compilateur nous renvoyait jusqu’ici un message assez peu… compréhensible.

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    format!("{_foo}", _foo = 6usize);
     
    error: invalid format string: expected `'}'`, found `'_'`
      |
    2 |     format!("{_foo}", _foo = 6usize);
      |             ^^^^^^^^

    Si nous analysons le message "tel quel", on remarque que le compilateur reste plutôt évasif. Le problème d’exhaustivité tend à se régler dans cette nouvelle version grâce à un message d’erreur spécifique à ce cas.

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    error: invalid format string: invalid argument name `_foo`
      |
    2 |     let _ = format!("{_foo}", _foo = 6usize);
      |                       ^^^^ invalid argument name in format string
      |
      = note: argument names cannot start with an underscore

    Mieux !

    Optimisation des types numériques non signés

    Une nouvelle collection de wrappers, censés représenter des entiers non signés et non nuls, pointe le bout de son nez pour amorcer une optimisation ciblant la mémoire consommée par les enums. Le cas le plus pertinent reste le stockage d’un entier non signé dans un conteneur Option<u8> (ou tout autre type d’entier non signé).

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    assert_eq!(std::mem::size_of::<Option<u8>>(), 2);

    Ici, 1 octet est réservé pour le type Option et le second l’est pour le primitif u8. Grâce aux nouveaux types NonZero, le compilateur est capable d’épargner la machine de la consommation de la structure.

    Comment ?

    Je ne suis, personnellement, pas parvenu à trouver d’explications officielles. Toutefois, m’est avis que rustc doit se permettre de ne pas effectuer d’alignement mémoire pour économiser le moindre octet. J’en suis arrivé à cette conclusion, car :

    • un entier u16 est codé sur 2 octets;
    • une énumération (ne contenant que des constantes simples) n’est codée que sur un seul octet;
    • size_of devrait renvoyer 3.


    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    assert_eq!(std::mem::size_of::<Option<u16>>(), 3);

    Et… ce n’est pas le cas.

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    thread 'main' panicked at 'assertion failed: `(left == right)`
      left: `4`,
     right: `3`', src/main.rs:4:5

    Maintenant, utilisons la structure NonZeroU16.

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    assert_eq!(std::mem::size_of::<Option<NonZeroU16>>(), 3);

    … ça ne tient toujours pas.

    Code Rust : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    thread 'main' panicked at 'assertion failed: `(left == right)`
      left: `2`,
     right: `3`', src/main.rs:4:5

    Ma théorie tombe donc à l’eau, j’ignore où est passé le dernier octet sur lequel est censée être codée l’instance de l’énumération.

    En attendant d’avoir la réponse, les performances sont tout de même au rendez-vous !

    Mise à jour de Cargo

    Enfin, nous terminerons sur une révision plutôt courte apportée à cargo.

    Il n’est désormais plus possible de publier une crate dont le manifest build.rs modifie le répertoire src lors du processus de compilation. src sera dorénavant considéré comme une ressource immuable lorsque le code est distribué.

    Annexe

    Comme précisé plus haut, je tenais à laisser en annexe un dépôt github présentant des architectures différentes d’allocateurs. L’auteur fournit des explications et des illustrations qui méritent clairement de s’y intéresser ne serait-ce que par curiosité.

    Note: L’allocateur est écrit en C++, mais la documentation se suffit à elle-même.

    Bonne lecture !

    Source(s)

    Avant de poster: FAQ Rust; FAQ Dart; FAQ Java; FAQ JavaFX.
    Vous souhaiteriez vous introduire au langage Rust ? C'est par ici ou ici !
    Une question à propos du langage ? N'hésitez pas à vous rendre sur le forum !


    Pour contribuer à la rubrique, vous pouvez me contacter par MP ou contacter directement la rédaction.

  5. #5
    Membre expert
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    avril 2016
    Messages
    766
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : avril 2016
    Messages : 766
    Points : 3 360
    Points
    3 360

    Par défaut

    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.

  6. #6
    Responsable Rust

    Avatar de Songbird
    Homme Profil pro
    Bidouilleur
    Inscrit en
    juin 2015
    Messages
    481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 20
    Localisation : France

    Informations professionnelles :
    Activité : Bidouilleur
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : juin 2015
    Messages : 481
    Points : 4 589
    Points
    4 589
    Billets dans le blog
    8

    Par défaut

    Hello,

    Effectivement, je n'y avais pas pensé. Merci pour l'élément de réponse !
    Avant de poster: FAQ Rust; FAQ Dart; FAQ Java; FAQ JavaFX.
    Vous souhaiteriez vous introduire au langage Rust ? C'est par ici ou ici !
    Une question à propos du langage ? N'hésitez pas à vous rendre sur le forum !


    Pour contribuer à la rubrique, vous pouvez me contacter par MP ou contacter directement la rédaction.

Discussions similaires

  1. La version stable de Devuan ASCII 2.0 est disponible
    Par Coriolan dans le forum Debian
    Réponses: 9
    Dernier message: 17/09/2018, 10h55
  2. Réponses: 6
    Dernier message: 09/06/2018, 20h50
  3. La version stable de Rust 1.24 est disponible
    Par Olivier Famien dans le forum Rust
    Réponses: 0
    Dernier message: 16/02/2018, 14h01
  4. Skype est désormais disponible en version finale sur Mac et Windows
    Par Stéphane le calme dans le forum Actualités
    Réponses: 3
    Dernier message: 08/11/2014, 12h57

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo