Journal L'édition 2018 de Rust est sortie !

Posté par (page perso) . Licence CC by-sa.
36
8
déc.
2018
Ce journal a été promu en dépêche : L’édition 2018 de Rust est sortie !.

Sommaire

L'édition 2018 du langage Rust est sortie. Ce journal est une traduction et un résumé de la documentation officielle.

Certaines nouveautés ne sont pas si nouvelles, mais c'est toujours utile d'en parler si vous n'avez pas trop suivi l'évolution du langage depuis un an.

Édition ? Jamais entendu parler de ça

Qu'est-ce qu'une édition en Rust ?

Il faut savoir qu'une nouvelle version de Rust sort toutes les 6 semaines. On peut donc vite être noyé dans les mises-à-jour et les nouveautés. Afin de pouvoir visualiser les nouveautés avec plus de recul, et surtout d'ajouter certains breaking changes, le langage a (et aura) une nouvelle édition tous les 2 ou 3 ans.

La première édition était celle de 2015, qui regroupe la version 1.0 et une partie des mises-à-jour qui ont suivi. Cette version 2018 regroupe les dernières nouveautés, dont une partie n'est pas accessible dans l'édition 2015.

Quid de la stabilité ?

Entendre parler de breaking change peut faire peur, mais l'équipe de Rust a bien étudié son coup. L'unité de compilation en Rust est le crate (caisse en anglais) contrairement au C par exemple, où c'est le fichier. Chacun des crates d'un projet peut utiliser une édition différente du langage. On peut créer un nouveau crate 2015 et utiliser un crate 2018, qui utilise de nouveaux mots-clés par exemple, sans problèmes.

Le langage peut donc avoir des changements incompatibles sans que le code des utilisateurs n'en soit impacté.

En revanche, la bibliothèque standard ne peut en aucun cas avoir de breaking changes puisque si on change un type entre les éditions 2015 à 2018 par exemple, on ne peut plus passer une instance de ce type d'un crate à l'autre.

Comment utiliser cette édition ?

Dans les faits, ça veut dire que :

  • Si vous voulez créer un nouvau projet, la commande cargo new créera un nouveau projet directement avec l'édition 2018. Concrètement, une nouvelle ligne est ajoutée dans le manifeste : edition = "2018"
  • Si vous voulez mettre à jour un projet existant, il suffit de lancer la commande cargo fix --edition, ce qui va rendre les sources compatibles avec la dernière version.

Quelles sont les nouveautés ?

Venons-en au fait: quoi de neuf ?

Le système de modules

Les chemins

Le système de modules de la première édition faisait partie de ces choses difficiles à appréhender pour un débutant. Par exemple, selon qu'on utilisait un chemin dans une instruction use ou dans le code, ça pouvait compiler ou pas : le système de chemins pouvait sembler quelque peu incohérent. Dans l'édition 2018, tout est harmonisé : dans tous les cas, il faut utiliser crate comme racine du chemin pour se référer au module de base, et self pour le module courant. L'équipe de Rust est encore en débat pour savoir lequel des deux sera le comportement par défaut en cas de chemin relatif.

Les dépendances externes

La directive extern crate n'est plus nécessaire quand on veut utiliser une dépendance externe. Effectivement, ça faisait doublon puisque l'information est dans tous les cas dans le manifeste du projet Cargo.toml.

Les macros

Les macros, qu'elles soit procédurales ou non, ressemblent de plus en plus aux autres items du langage. On les importe maintenant dans le scope avec use, tout comme le reste : use mon::chemin::ma_macro;. Il y a encore du travail à faire sur les macros (hygiène, macros 2.0, …), mais elles pourront bientôt s'utiliser à tout point de vue comme des fonctions.

Quel fichier pour quel module

Maintenant, quand on veut mettre un sous-module dans un dossier, on n'a plus besoin du fichier mod.rs. Avant :

|
|- foo
|  |- mod.rs
|  |- autre_module.rs

Après :

|
|- foo.rs
|- foo
|  |- autre_module.rs

Ça permet de ne pas avoir tout un tas de fichiers mod.rs ouverts en même temps dans un éditeur (c'est vrai qu'on s'y perdait vite dans les gros projets).

De nouveaux types de modifieurs de visibilité

On peut mettre tout un tas de paramètres au mot clés pub pour un comportement un peu similaire au mot-clé friend en C++ : pub(crate), pub(a::b::c), etc. Il y a plus de détails dans la documentation du langage.

Directive use imbriquée

L'utilisation de use est plus souple. On peut maintenant marquer :

use std::{
    fs::File,
    io::Read,
    path::{
        Path,
        PathBuf
    }
};

Système de trait

Maintenant, il existe une syntaxe plus explicite et symétrique pour le dispatch dynamique vs statique :

// Géré à la compilation via monomorphisation :
fn foo(it: impl Iterator<Item = i32>) {
    for i in it {
        // ...
    }
}
// Géré pendant le runtime :
fn foo(it: Box<dyn Iterator<Item = i32>>) {
    for i in it {
        // ...
    }
}

La notation impl peut remplacer l'introduction d'un type générique (sauf dans les cas les plus complexes) en simplifiant la syntaxe. Ainsi, on aurait pu écrire :

fn foo<T>(it: T) where T: Iterator<Item = i32> {
    for i in it {
        // ...
    }
}

Simplification concernant les types lifetime

Lifetimes non lexicaux

Il y aurait beaucoup à dire à ce sujet, mais le borrow-checker (la partie du compilateur qui vérifie la sécurité du code) a été réimplémenté avec un algorithme différent. Il est maintenant plus souple, dans le sens ou il élimine plus de faux-positifs. Le précédent borrow-checker refusait certaines choses qui auraient dû être acceptées, par exemple :

let mut v = vec![1, 2, 3];
v.push(v.len())

Pattern matching plus intelligent

Le pattern matching n'oblige plus à faire des contorsions quand on match des références. Par exemple, avant on devait écrire :

let s: &Option<String> = &Some("hello".to_string());

match s {
    // On match s avec `&Some(_)` puisque s est une référence,
    // et pour la valeur interne on doit marquer `ref s`
    // puisqu'on ne peut pas déplacer le contenu d'une donnée
    // qui a été empruntée.
    &Some(ref s) => println!("s is: {}", s),
    _ => (),
};

En 2018, le code est plus simple, et plus intuitif :

let s: &Option<String> = &Some("hello".to_string());

match s {
    // Le compilateur comprend qu'on veut récupérer une référence
    // sur la valeur interne. La référence est pour ainsi dire passée
    // de l'Option à la valeur interne.
    Some(s) => println!("s is: {}", s),
    _ => (),
};

Simplification dans l'écriture des lifetimes génériques

Il s'agit d'un certains nombre de cas de figure où l'écriture était inutile, redondante, etc. En vrac :

  • Tout comme les types de données, les types lifetime ont un identifieur anonyme '_. On l'utilise dans le cas d'un type avec un lifetime générique : Foo<'_>.
  • Plus besoin de spécifier le lifetime générique quand on implémente un trait pour une référence : impl Trait for &Foo { /* etc. */ }
  • Plus besoin de spécifier l'interdépendance des types données génériques avec les types lifetime génériques dans le cas d'une struct, par exemple : T: 'a.

Autres

Voici une liste de fonctionalités plus complexes, et donc que je détaillerai moins :

Notations pour le code asynchrone

Il sera plus simple d'écrire du code asynchrone grâce aux mots-clés async et await. Cette fonctionalité n'est pas encore stable, mais les mots-clés sont réservés pour l'édition 2018.

SIMD, 128 bits

Sont supportés officiellement les opérations SIMD et les types entiers sur 128 bits i128 et u128.

Macros procédurales

Tous les types de macros procédurales ont été stabilisés pour 2018 :
- les instructions derive : #[derive(Foo)]
- les attributs : #[foo(/* etc. */)]
- les macros appelées comme des fonctions : foo!(/* etc. */)

Zero ou une occurence dans les macros

Maintenant, les mêmes répéteurs que pour les regexs existent dans l'implémentation des macros :
- * pour zero répétition ou plus,
- + pour une répétition ou plus,
- ? pour zero ou une occurence.

On peut faire du pattern-matching avec des slices

fn main() {
    bonjour(&[]);
    // sortie: Hé, il n'y a personne ici.

    bonjour(&["Linus"]);
    // sortie: Coucou, Linus, j'ai l'impression que tu es tout seul.

    bonjour(&["Linus", "Richard"]);
    // sortie: Coucou, Linus et Richard. Content de voir que vous êtes au moins 2 !

    bonjour(&["Linus", "Richard", "Lennart"]);
    // sortie: Bonjour à tous, j'ai l'impression que nous sommes 3 aujourd'hui.
}

fn bonjour(people: &[&str]) {
    match people {
        [] => println!("Hé, il n'y a personne ici."),
        [tout_seul] => println!("Coucou, {}, j'ai l'impression que tu es tout seul.", tout_seul),
        [premier, second] => println!("Coucou, {} et {}. \
            Content de voir que vous êtes au moins 2 !", premier, second),
        _ => println!("Bonjour à tous, j'ai l'impression que nous sommes {} aujourd'hui.", people.len()),
    }
}

Conclusion

Le langage a beaucoup avancé au cours des 3 dernières années, avec de nouvelles fonctionalités excitantes, et des simplifications bienvenues. Je ne peux que vous conseiller de lire la documentation officielle à ce sujet.

La communautés attend encore avec impatience bien d'autres choses (je vous invite à voir la liste des RFC pour vous faire une idée) et je suis convaincu que la prochaine édition sera tout aussi riche en nouveautés que celle-ci.

  • # Nouille cassante

    Posté par (page perso) . Évalué à 2.

    et surtout d'ajouter certains breaking changes

    Il ne faut jamais introduire de breaking changes. C'est le 11° commandement.

    Incubez l'excellence sur https://linuxfr.org/board/

    • [^] # Re: Nouille cassante

      Posté par (page perso) . Évalué à 9.

      Ca dépend.
      C'est en évitant les breaking changes qu'on se retrouve parfois avec des gros bloatwares parce qu'on a pas voulu "brusquer l'utilisateur".

    • [^] # Re: Nouille cassante

      Posté par (page perso) . Évalué à 6.

      Un paragraphe en dessous, j'explique que ce ne sont pas vraiment des breaking changes puisqu'on peut spécifier l'édition du langage dans le manifeste du projet. Tout ce qui compilait continuera à compiler sans soucis (et heureusement, encore).

  • # Très jolie future dépêche, qui pourrait encore être enrichie ;)

    Posté par . Évalué à 1. Dernière modification le 08/12/18 à 23:50.

    Boiethios, j'observe que ton journal est devenu une dépêche en cours de modération (c'est visible dans la page dédiée à l'espace de rédaction collaborative (en bas de page), accessible au utilisateurs ayant un compte), or il y avait une dépêche en cours de rédaction sur le même sujet depuis cet été, que j'ai participé à améliorer un tout petit peu ces derniers jours, mais dont une section n'était pas finalisée. Du coup je me suis permis de proposer d'intégrer des éléments de la dépêche en cours de rédaction dans ton journal transformé en dépêche en cours de modération (pour publication prochaine) — j'ai rédigé quelques commentaires dans la tribune de rédaction (un tchat) — et du coup je me permets de te solliciter pour formuler ton point de vue à ce sujet dans ladite tribune. J'ai dans l'idée que ça pourrait favoriser des choix optimisés de la part des modérateurs, pour produire une dépêche de top qualitaÿ ;)

    • [^] # Re: Très jolie future dépêche, qui pourrait encore être enrichie ;)

      Posté par (page perso) . Évalué à 1.

      Ah, désolé, j'avais regardé dans la liste des dépêches s'il n'y avait pas déjà une rédaction en cours, et je n'avais rien vu. Oui, je serai ravi de donner un coup de main !

      • [^] # Re: Très jolie future dépêche, qui pourrait encore être enrichie ;)

        Posté par . Évalué à -1.

        J'insiste, exprime-toi dans la tribune de rédaction pour donner ton avis, parce que sinon le protocole classique c'est qu'entre modérateurs ils vont seuls décider de publier ta dépêche (en l'état ou en ajoutant des éléments de celle qui est en cours de rédaction, c'est leur libre choix, ton journal étant sous licence permissive CC by-sa). Or, par exemple, si tu améliores la liste à puces des évolutions de Rust depuis sa sortie (1), ça pourrait être intéressant pour enrichir ton travail. Pour le faire dans l'ordre, je pense qu'après avoir consulté la dépêche en cours de rédaction, tu pourrais publier ton avis sur la tribune de rédaction (la tribune générale, dans la page d'accueil de l'espace de rédaction collaborative), observer le retour des modérateurs et discuter avec eux. J'imagine qu'il est y compris possible aux modérateurs de remettre la dépêche correspondant à ton journal dans le status en cours de rédaction pour te permettre d'y retoucher. A voir avec eux.

        (1) liste qui est assez détaillée, fruit d'un parcours de tous les changelogs de Rust depuis la version 1.0 en sélectionnant les plus importants. StyMaar a fait le boulot initial, cf. la tribune (tchat) interne à la dépêche en cours de rédaction, le tout premier commentaire dans l'ordre chronologique.

      • [^] # Syntaxe pour l'enrichissement typographique sur les tribunes de Linuxfr.org

        Posté par . Évalué à 0.

        Note que pour les tribunes (tchats) sur Linuxfr.org, la syntaxe n'est pas du Markdown mais du HTML partiellement implémenté.

        Concrètement, tu as ceci à disposition :

        • pour l'italique : encadrement par les balises <i> et </i> ;
        • pour le gras : encadrement par les balises <b> et </b> ;
        • pour le souligné (oui, oui, il y a même le soulignement) : encadrement par les balises <u> et </u> ;
        • pour biffer (alias raturer) : encadrement par les balises <s> et </s> ;
        • pour mettre un lien : tu mets simplement l'URL et c'est transformé en un lien avec le libellé [URL] (le formalisme HTML tel que <a title="Titre du lien" href="http://www.adresse-du-lien.fr">Ceci est un lien</a> ne fonctionne pas).
      • [^] # Re: Très jolie future dépêche, qui pourrait encore être enrichie ;)

        Posté par . Évalué à -1.

        Je t'ai adressé à l'instant deux commentaires dans la tribune interne à la dépêche sur le même sujet (qui avait et a encore temporairement le status "en cours de rédaction"). Ton journal étant désormais publié en dépêche sans avoir intégré les ajouts de l'autre dépêche (à mon avis principalement parce que tu as tardé à manifester autre chose que ton ravissement à l'idée de donner un coup de main), je croise les doigts pour que tu trouves la motivation / le temps de nous gratifier d'un commentaire, sous ta dépêche, incluant ce que tu estimes complémentaires, tiré de l'autre dépêche.

  • # Loué, soit Rust, le seul langage de programmation éthique, sans peur et sans concurrents!

    Posté par . Évalué à 0.

    Amen

    Depending on the time of day, the French go either way.

  • # compatibilité

    Posté par (page perso) . Évalué à 3.

    En revanche, la bibliothèque standard ne peut en aucun cas avoir de breaking changes puisque si on change un type entre les éditions 2015 à 2018 par exemple, on ne peut plus passer une instance de ce type d'un crate à l'autre.

    Pourquoi pas? On pourrait imaginer plein de changements qui font que le type reste compatible. Par example, si on trouve que retourner un &str est plus efficace que de retourner un String, pour une fonction:

    impl Bar {
       #[edition(2018)]
       name(&self) -> String { ... }
    
       #[edition(2021)]
       name(&self) -> &str { ... }
    }

    On peut créer un nouveau crate 2015 et utiliser un crate 2018, qui utilise de nouveaux mots-clés par exemple, sans problèmes.

    Sauf si il y a des macros qui génère du code incompatible, non?

    • [^] # Re: compatibilité

      Posté par (page perso) . Évalué à 1.

      Je ne pense pas que ça fonctionne. Le défi, ce n'est pas que quelqu'un qui utilise std voit toujours son projet compiler. Le problème est le suivant :

      • Je crée mon projet en 2018. J'utilise la version de 2018 de std::Bar.
      • Je référence un crate qui utilise std::Bar en version 2015 dans une fonction publique, genre get_bar() -> std::Bar.
      • Que se passe-t-il quand j'appelle get_bar() ? Je récupère un objet std::Bar[2015] qui n'est pas celui que je voulais : std::Bar[2018].
      • [^] # Re: compatibilité

        Posté par (page perso) . Évalué à 3.

        Que se passe-t-il quand j'appelle get_bar() ? Je récupère un objet std::Bar[2015] qui n'est pas celui que je voulais : std::Bar[2018].

        std::Bar à le même layout mémoire dans les deux cas. Seul la signature (et implémentation) de certaines fonction change.

        • [^] # Re: compatibilité

          Posté par (page perso) . Évalué à 1.

          Un objet en Rust n'est pas défini par sa signature mémoire. Si on prend un crate en version 1, et que sans en rien changer on le publie en version 2, tous les types de ces 2 crates seront différents. Après, c'est vrai que la lib std est spéciale, mais je soupçonne que ce n'est pas possible (du moins, pas dans l'état actuel des choses).

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.