Macros Rust : Comment les utiliser pour améliorer votre code
La génération de code est une fonctionnalité que vous trouverez dans la plupart des langages de programmation modernes. Elle peut vous aider à réduire le code » boilerplate » et la duplication de code, à définir des langages spécifiques à un domaine (DSL) et à implémenter une nouvelle syntaxe.
Rust fournit un système de macro puissant qui vous permet de générer du code au moment de la compilation pour une programmation plus sophistiquée.
Introduction aux macros Rust
Les macros sont un type de métaprogrammation que vous pouvez exploiter pour écrire du code qui écrit du code. En Rust, une macro est un morceau de code qui génère d’autres codes au moment de la compilation.
Les macros Rust sont une fonctionnalité puissante qui vous permet d’écrire du code qui génère d’autres codes au moment de la compilation afin d’automatiser les tâches répétitives. Les macros Rust permettent de réduire la duplication du code et d’augmenter la maintenabilité et la lisibilité du code.
Vous pouvez utiliser les macros pour générer n’importe quoi, depuis de simples extraits de code jusqu’à des bibliothèques et des frameworks. Les macros diffèrent des fonctions Rust car elles opèrent sur le code plutôt que sur les données au moment de l’exécution.
Définition des macros en Rust
Vous définirez les macros à l’aide de la fonction macro_rules ! macro. La macro macro_rules ! prend en entrée un motif et un modèle. Rust compare le motif au code d’entrée et utilise le modèle pour générer le code de sortie.
Voici comment vous pouvez définir des macros en Rust :
Le code définit un say_hello qui génère du code pour imprimer « Hello, world ! ». Le code correspond à la macro () à une entrée vide et à la syntaxe println ! génère le code de sortie.
Voici le résultat de l’exécution de la macro dans la zone principal principale :
Les macros peuvent prendre des arguments d’entrée pour le code généré. Voici une macro qui prend un seul argument et génère du code pour imprimer un message :
Le dire_message prend la macro $message et génère du code pour imprimer l’argument à l’aide de la macro println ! . La macro expr permet de faire correspondre l’argument à n’importe quelle expression de Rust.
Types de macros Rust
Rust propose trois types de macros. Chacun de ces types de macros répond à des besoins spécifiques et possède sa propre syntaxe et ses propres limites.
Macros procédurales
Les macros procédurales sont considérées comme le type le plus puissant et le plus polyvalent. Les macros procédurales vous permettent de définir une syntaxe personnalisée qui génère simultanément du code Rust. Vous pouvez utiliser les macros procédurales pour créer des macros dérivées personnalisées, des macros de type attribut personnalisées et des macros de type fonction personnalisées.
Vous utiliserez les macros derive personnalisées pour implémenter automatiquement les structs et les traits enum. Des packages populaires comme Serde utilisent une macro derive personnalisée pour générer du code de sérialisation et de désérialisation pour les structures de données Rust.
Les macros de type attribut personnalisé sont pratiques pour ajouter des annotations personnalisées au code Rust. Le cadre web Rocket utilise une macro de type attribut personnalisé pour définir des itinéraires de manière concise et lisible.
Vous pouvez utiliser des macros de type fonction personnalisée pour définir de nouvelles expressions ou instructions Rust. Le crate Lazy_static utilise une macro de type fonction personnalisée pour définir la fonction lazy-initialized statiques initialisées paresseusement.
Voici comment vous pouvez définir une macro procédurale qui définit une macro dérivée personnalisée :
Les utiliser importent les caisses et les types nécessaires à l’écriture d’une macro procédurale Rust.
Le programme définit une macro procédurale qui génère l’implémentation d’un trait pour une structure ou un enum. Le programme invoque la macro avec le nom MonTrait dans l’attribut derive de la structure ou de l’énumération. La macro prend une valeur TokenStream contenant le code analysé dans un arbre syntaxique abstrait (AST) avec l’attribut parse_macro_input ! macro.
La macro nom est l’identificateur de la structure dérivée ou de l’enum, la variable quote ! La macro génère un nouvel AST représentant l’implémentation de MonTrait pour le type qui est finalement retourné en tant que TokenStream avec le dans le méthode.
Pour utiliser la macro, vous devez l’importer à partir du module dans lequel vous l’avez déclarée :
Lors de la déclaration de la structure ou de l’enum qui utilise la macro, vous ajouterez la méthode # au début de la déclaration.
La déclaration de la structure avec l’attribut s’étend à une implémentation de la macro MonTrait pour la structure :
L’implémentation vous permet d’utiliser les méthodes du trait MonTrait sur MyStruct instances.
Macros d’attributs
Les macros d’attributs sont des macros que vous pouvez appliquer aux éléments Rust tels que les structs, les enums, les fonctions et les modules. Les macros d’attributs se présentent sous la forme d’un attribut suivi d’une liste d’arguments. La macro analyse l’argument pour générer du code Rust.
Vous utiliserez les macros d’attributs pour ajouter des comportements et des annotations personnalisés à votre code.
Voici une macro d’attribut qui ajoute un attribut personnalisé à une structure Rust :
La macro prend une liste d’arguments et une définition de structure et génère une structure modifiée avec le comportement personnalisé défini.
La macro prend deux arguments en entrée : l’attribut appliqué à la macro (analysé avec l’attribut parse_macro_input ! ) et l’élément (analysé avec la macro parse_macro_input ! ). La macro utilise l’élément quote ! pour générer le code, y compris l’élément d’entrée original et un élément supplémentaire de type impl qui définit le comportement personnalisé.
Enfin, la fonction renvoie le code généré sous la forme d’un fichier TokenStream avec le into() .
Règles de macro
Les règles de macro sont le type de macros le plus simple et le plus flexible. Les règles de macro vous permettent de définir une syntaxe personnalisée qui s’étend au code Rust au moment de la compilation. Les règles de macro définissent des macros personnalisées qui correspondent à n’importe quelle expression ou déclaration Rust.
Vous utiliserez les règles de macro pour générer du code de base afin d’abstraire les détails de bas niveau.
Voici comment vous pouvez définir et utiliser les règles de macro dans vos programmes Rust :
Le programme définit une make_vector ! une macro qui crée un nouveau vecteur à partir d’une liste d’expressions séparées par des virgules dans la zone principal principale.
À l’intérieur de la macro, la définition du motif correspond aux arguments transmis à la macro. La fonction $( $x:expr ),* permet de faire correspondre toutes les expressions séparées par des virgules et identifiées comme $x.
La syntaxe $( ) dans le code d’expansion itère sur chaque expression de la liste des arguments transmis à la macro après la parenthèse fermante, indiquant que les itérations doivent se poursuivre jusqu’à ce que la macro traite toutes les expressions.
Organisez efficacement vos projets Rust
Les macros Rust améliorent l’organisation du code en vous permettant de définir des modèles de code réutilisables et des abstractions. Les macros peuvent vous aider à écrire un code plus concis et plus expressif sans duplication dans les différentes parties du projet.
De plus, vous pouvez organiser les programmes Rust en crates et en modules pour une meilleure organisation du code, une meilleure réutilisation et une meilleure interopérabilité avec d’autres crates et modules.
S’abonner à notre lettre d’information
Peut-on utiliser des macros en Rust ?
La forme de macros la plus utilisée en Rust est la macro déclarative. Elles sont aussi parfois appelées « macros par exemple », « macros macro_rules ! » ou simplement « macros ». À la base, les macros déclaratives vous permettent d’écrire quelque chose de similaire à une expression de correspondance en Rust.
Quel est l’intérêt des macros en Rust ?
Une macro en Rust est un morceau de code qui génère un autre morceau de code. Les macros génèrent du code en fonction des entrées, simplifient les schémas répétitifs et rendent le code plus concis. Les macros Rust nous permettent simplement d’écrire du code qui écrit plus de code, ce qui est également connu sous le nom de métaprogrammation.
Comment créer une macro procédurale en Rust ?
Les macros procédurales doivent être définies dans un crate avec le type de crate proc-macro . Comme les fonctions, elles doivent soit retourner une syntaxe, soit paniquer, soit boucler sans fin :
- Macros de type fonction – custom !( )
- Macros dérivées – #[derive(CustomDerive)]
- Macros d’attributs – #[CustomAttribute]
Quelle est la différence entre les macros procédurales et les macros Rust ?
Les macros déclaratives sont définies à l’aide de macro_rules ! et ce sont de simples transformations (déclaratives), donc ce sont surtout des aides pratiques. Les macros procédurales sont des programmes Rust à part entière qui manipulent le flux de jetons. Les macros procédurales sont beaucoup plus puissantes car vous disposez d’un langage de programmation complet.