Les macros vous permettent d'écrire du code qui écrit un autre code. Découvrez le monde étrange et puissant de la métaprogrammation.

La génération de code est une fonctionnalité que vous trouverez dans la plupart des langages de programmation modernes. Il peut vous aider à réduire le code passe-partout et la duplication de code, à définir des langages spécifiques à un domaine (DSL) et à implémenter une nouvelle syntaxe.

Rust fournit un puissant système de macros 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. Dans Rust, une macro est un morceau de code qui génère un autre code au moment de la compilation.

Les macros Rust sont une fonctionnalité puissante qui vous permet d'écrire du code qui génère un autre code au moment de la compilation pour automatiser les tâches répétitives. Les macros de Rust aident à réduire la duplication de code et à augmenter la maintenabilité et la lisibilité du code.

Vous pouvez utiliser des macros pour générer n'importe quoi, des simples extraits de code aux bibliothèques et aux frameworks. Les macros diffèrent de Fonctions de rouille car ils fonctionnent sur du code plutôt que sur des données au moment de l'exécution.

Définir des macros dans Rust

Vous allez définir des macros avec le macro_règles ! macro. Le macro_règles ! La macro prend un motif et un modèle en entrée. Rust compare le modèle 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 dans Rust :

macro_règles ! dis bonjour {
() => {
imprimez !("Bonjour le monde!");
};
}

fnprincipal() {
dis bonjour!();
}

Le code définit un dis bonjour macro qui génère du code pour afficher "Hello, world!". Le code correspond à () syntaxe contre une entrée vide et la imprimez ! macro génère le code de sortie.

Voici le résultat de l'exécution de la macro dans le principal fonction:

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 :

macro_règles ! dire_message {
($message: expr) => {
imprimez !("{}", $message);
};
}

Le dire_message la macro prend la $message argument et génère du code pour imprimer l'argument en utilisant le imprimez ! macro. Le expr la syntaxe correspond à l'argument avec n'importe quelle expression Rust.

Types de macros de rouille

Rust fournit trois types de macros. Chacun des types de macro sert à des fins spécifiques, et ils ont leur syntaxe et leurs 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 des macros procédurales pour créer des macros dérivées personnalisées, des macros personnalisées de type attribut et des macros personnalisées de type fonction.

Vous utiliserez des macros de dérivation personnalisées pour implémenter automatiquement des structures et des traits d'énumération. Les packages populaires tels que Serde utilisent une macro de dérivation 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 personnalisées de type attribut sont pratiques pour ajouter des annotations personnalisées au code Rust. Le framework Web Rocket utilise une macro personnalisée de type attribut pour définir les itinéraires de manière concise et lisible.

Vous pouvez utiliser des macros de type fonction personnalisées pour définir de nouvelles expressions ou instructions Rust. La caisse Lazy_static utilise une macro de type fonction personnalisée pour définir le initialisé paresseux variables statiques.

Voici comment vous pouvez définir une macro procédurale qui définit une macro de dérivation personnalisée :

utiliser proc_macro:: TokenStream;
utiliser devis:: devis;
utiliser syn ::{DeriveInput, parse_macro_input} ;

Le utiliser Les directives importent les caisses et les types nécessaires pour écrire une macro procédurale Rust.

#[proc_macro_derive (MonTrait)]
pubfnma_macro_derivée(entrée: TokenStream) -> TokenStream {
laisser ast = parse_macro_input!(entrée comme DériverEntrée);
laisser nom = &ast.ident;

laisser gen = citation! {
mettre en œuvre Mon trait pour #nom {
// implémentation ici
}
};

gen.into()
}

Le programme définit une macro procédurale qui génère l'implémentation d'un trait pour une structure ou une énumération. Le programme appelle la macro avec le nom Mon trait dans l'attribut de dérivation de la structure ou de l'énumération. La macro prend un TokenStream objet en entrée contenant le code analysé dans un arbre de syntaxe abstraite (AST) avec le parse_macro_input ! macro.

Le nom variable est la structure dérivée ou l'identifiant enum, le citation! La macro génère un nouvel AST représentant l'implémentation de Mon trait pour le type qui est finalement renvoyé en tant que TokenStream avec le dans méthode.

Pour utiliser la macro, vous devrez importer la macro depuis le module dans lequel vous l'avez déclarée :

// en supposant que vous avez déclaré la macro dans un module my_macro_module

utiliser my_macro_module:: my_derive_macro;

En déclarant la structure ou l'énumération qui utilise la macro, vous ajouterez le #[dériver (MonTrait)] attribut en haut de la déclaration.

#[dériver (MonTrait)]
structureMaStructure {
// champs ici
}

La déclaration de structure avec l'attribut se développe en une implémentation de la Mon trait trait pour la structure :

mettre en œuvre Mon trait pour MaStructure {
// implémentation ici
}

L'implémentation vous permet d'utiliser des méthodes dans le Mon trait trait sur MaStructure instances.

Macros d'attributs

Les macros d'attributs sont des macros que vous pouvez appliquer aux éléments Rust tels que les structures, les énumérations, les fonctions et les modules. Les macros d'attributs prennent 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 des macros d'attributs pour ajouter des comportements personnalisés et des annotations à votre code.

Voici une macro d'attribut qui ajoute un attribut personnalisé à une structure Rust :

// importation de modules pour la définition de la macro
utiliser proc_macro:: TokenStream;
utiliser devis:: devis;
utiliser syn ::{parse_macro_input, DeriveInput, AttributeArgs} ;

#[proc_macro_attribute]
pubfnma_macro_attribut(attr: TokenStream, élément: TokenStream) -> TokenStream {
laisser args = parse_macro_input!(attr comme AttributeArgs );
laisser entrée = parse_macro_input! (élément comme DériverEntrée);
laisser nom = &input.ident;

laisser gen = citation! {
#saisir
mettre en œuvre #nom {
// comportement personnalisé ici
}
};

gen.into()
}

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 (parsé avec le parse_macro_input ! macro) et l'élément (parsé avec la parse_macro_input ! macro). La macro utilise le citation! macro pour générer le code, y compris l'élément d'entrée d'origine et un élément supplémentaire mettre en œuvre bloc qui définit le comportement personnalisé.

Enfin, la fonction renvoie le code généré sous forme de TokenStream avec le dans() méthode.

Règles 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 instruction de rouille.

Vous utiliserez des règles de macro pour générer du code passe-partout afin d'abstraire les détails de bas niveau.

Voici comment vous pouvez définir et utiliser des règles de macro dans vos programmes Rust :

macro_règles ! make_vector {
( $( $x: expression ),* ) => {
{
laissermuet v = Vec::nouveau();
$(
v.push($x);
)*
v
}
};
}

fnprincipal() {
laisser v = make_vector ![1, 2, 3];
imprimez !("{:?}", v); // affiche "[1, 2, 3]"
}

Le programme définit un make_vector ! une macro qui crée un nouveau vecteur à partir d'une liste d'expressions séparées par des virgules dans le principal fonction.

Dans la macro, la définition du modèle correspond aux arguments passés à la macro. Le $( $x: expression ),* la syntaxe correspond à toutes les expressions séparées par des virgules identifiées comme $x.

Le $( ) syntaxe dans le code d'expansion itère sur chaque expression dans la liste des arguments passés à la macro après la parenthèse fermante, indiquant que les itérations doivent continuer jusqu'à ce que la macro traite tous 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 et des abstractions réutilisables. Les macros peuvent vous aider à écrire un code plus concis et expressif sans duplications entre les différentes parties du projet.

En outre, vous pouvez organiser les programmes Rust en caisses et modules pour une meilleure organisation du code, une réutilisation et une interopérabilité avec d'autres caisses et modules.