URL: https://linuxfr.org/users/morovaille/journaux/port-de-taptempo-en-rust Title: Port de taptempo en Rust Authors: Morovaille Date: 2018年03月03日T20:00:08+01:00 License: CC By-SA Tags: taptempo et rust Score: 14 Comme promis, voici un petit journal sur mon [port de taptempo en `Rust`](https://gitlab.com/Boiethios/tempotap.git). Je n'ai pas porté le mécanisme d'internationalisation, puisque finalement on peut le faire comme dans le code `C++`, avec `gettext`. Ce n'est pas le plus intéressant du projet, et il n'existe pas de mécanisme d'internationalisation que je trouve vraiment satisfaisant et idiomatique pour le moment. # Description du Rust Pour ceux qui ne connaissent pas le langage, la façon la plus simple de le décrire est de dire que c'est un croisement entre le `C` et le `Haskell`. Comme le `C`, il est bas-niveau (on peut faire en `Rust` ce qu'on ferait en `C`: programmation de micro-contrôleurs, d'OS, etc.) mais il tire du `Haskell` le fait d'être "orienté expression", sa saveur fonctionnelle, la richesse de son système de types. Ce qui fait sa particularité et son immense avantage, c'est qu'il est memory safe et thread safe. Le langage garantit qu'il n'y a pas de pointeur invalide ou de "data race", le tout sans ramasse-miettes. # Préparation du projet Un autre point est que comme beaucoup de langages modernes, le systèmes de dépendances (appelées **crates** en `Rust`) est très simple. On rajoute une ligne dans le manifeste du projet (appelé `Cargo.toml`) et le gestionnaire de build télécharge la dépendance et l'inclus dans le build. C'est tellement idiomatique et facile qu'il y a certaines dépendances qui n'ajoutent qu'une petite fonction ou macro utilitaire. Voici les outils que j'ai sélectionnés: - Ce projet utilise les options de lignes de commande : le crate indispensable pour ceci est `StructOpt` qui ajoute des macros procédurales permettant de récupérer les paramètres entrés en ligne de commande très facilement. - Ensuite, j'ajoute un crate pour avoir une queue circulaire (puisque c'est la structure de données que nous avons en interne). En l’occurrence, je n'ai pas trouvé de crate satisfaisant, et donc j'ai écrit le mien (il n'est pas encore publié puisqu'il est encore en chantier, mais ça viendra ;) ). # Gestion des paramètres J'ai ajouté une nouvelle structure que j'appelle de façon originale `Params`. Vous verrez au-dessus la ligne suivante: ```rust #[derive(Debug, StructOpt)] ``` qui permet de faire dériver notre `struct` des [`trait`s](https://fr.wikipedia.org/wiki/Trait_(programmation)) `Debug` et le fameux `StructOpt`. Le trait `Debug` devrait être implémenté sur toutes les structures de données puisqu'il permet de leur donner une représentation textuelle. Pratique pour le débug dans la console ou dans un log. Le trait `StructOpt` va permettre la génération du code pour la gestion des paramètres. Quand on tape ce genre de lignes, en interne une macro est invoquée qui va parser l'[AST](https://fr.wikipedia.org/wiki/Arbre_syntaxique_abstrait) de la `struct` et en faire ce qu'on veut. Une macro procédurale est un générateur de code. Du coup, je vais ajouter une donnée dans ma `struct`: ```rust precision: usize, ``` et au-dessus, je vais donner des informations à `StructOpt`: le code court et long pour ajouter le paramètre (`-p` et `--precision`), la valeur par défaut, la documentation, etc. Tout est dans la doc du crate. J'implémente ensuite un trait pour dire que notre structure a des valeurs par défaut. Je crée les données avec les infos passées en ligne de commande, et ensuite je modifie les chiffres en fonction des bornes. Contrairement à `Ada`, on ne peut pas faire de type numérique borné *pour le moment*. Le système de type n'est pas achevé (le langage est encore jeune), mais quand ça viendra, ce type existera ! D'ici fin 2018 ça devrait être fait. # La structure `App` Notre structure principale va ressembler à l'objet `C++` originel. J'implémente le trait pour donner une valeur par défaut comme pour `StructOpt` (c'est vaguement un équivalent du constructeur par défaut en `C++`). Ensuite, je suppose que je n'ai pas besoin de décrire dans le détail toutes les fonctions dans `App`, le nom est explicite et ça ressemble au code d'origine. Je vais juste aborder quelques spécificités du langage. En `Rust`, une opération qui échoue retourne un type `Result` qui peut avoir deux valeurs: `Ok` avec l'objet attendu ou `Err` avec le type d'erreur qui s'est produite. Ça permet de gérer élégamment les erreurs sans mécanisme d'exceptions. Le langage est "orienté expressions", donc on peut écrire des choses du genre : ```rust fn must_continue(reader: &mut Lines) -> Result { match reader.next() { None => Ok(false), Some(r) => r.map(|s| s != "q") } } ``` Cette fonction doit se lire ainsi : - Si on n'a pas de nouvelle ligne (l'utilisateur a tapé `CTRL` + `D`), l'opération a réussi et on ne doit pas continuer: `Ok(false)`. - Si on a une ligne, on prend le résultat, et avec `map` on transforme le `Ok(String)` en `Ok(bool)` en comparant la résultat avec `"q"`. # La macro `print_now` Comme dans la plupart des langages, la sortie standard est buffurisée et donc rien n'est affiché tant qu'il n'y a pas de retour à la ligne. Du coup, j'ai écrit une petite macro qui vide les buffers après avoir appelé `print`. Ça permet de voir le fonctionnement des macros (classiques cette fois-ci, en opposition aux macros procédurales dont j'ai parlé plus haut): contrairement aux macros du `C`/`C++`, celles du `Rust` sont sémantiques, donc on doit faire matcher avec des choses qui ont du sens, et non pas "aveuglément". En l’occurrence, je récupère des expressions (`$e:expr`) ; Le `,*` permet de dire qu'on veut en récupérer 0 ou plus séparées par des virgules. La syntaxe peut sembler un peu ésotérique, mais le mécanisme est puissant et sécurisé. # Le main Le main est simple, il va créer un objet de type `App` et lancer la méthode `run` puis vérifier le retour d'erreur. # Conclusion J'espère que je vous ai donné envie de découvrir un peu plus ce langage qui est selon moi l'alternative la plus crédible au `C`. C'est un sentiment incroyable de faire un développement bas-niveau et de savoir que si le code compile, on ne tombe pas sur des erreurs non prévues au runtime (du genre segfault). Ceci permet de faire des réusinages massifs sans crainte, puisque le compilateur sait si on a fait quelque chose d'invalide au niveau de la mémoire. En tout cas, j'ai moi-même progressé avec ce projet, et je suis parti pour publier au moins 3 crates différents. Le `Rust` est beaucoup basé sur le principe de l'ouverture du code (pour être publié en ligne, un crate doit être open-source) et ça fait toujours plaisir d'apporter sa pierre à l'édifice.

AltStyle によって変換されたページ (->オリジナル) /