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 standard et la duplication du code, à définir des langages spécifiques à un domaine (DSL) et à mettre en œuvre 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 utiliser 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.

Lire  Comment savoir si quelqu'un a supprimé ou désactivé son compte TikTok ?

Vous pouvez utiliser les macros pour générer n’importe quoi, du simple extrait de code aux bibliothèques et 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éfinir des macros en Rust

Vous définirez les macros à l’aide de la fonction macro_rules ! macro. La macro macro_rules ! La macro 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 définir des macros en Rust :

 macro_rules! say_hello {
    () => {
        println!("Hello, world!");
    };
}

fn main() {
    say_hello!();
}

Le code définit une say_hello macro qui génère du code pour imprimer « Hello, world ! ». Le code correspond à la macro () contre 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 le fichier principal fonction :

La sortie d'un programme qui imprime

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_rules! say_message {
    ($message:expr) => {
        println!("{}", $message);
    };
}

Le dire_message prend la macro $message et génère du code pour imprimer l’argument à l’aide de la fonction println ! macro. La macro expr La syntaxe fait correspondre l’argument à n’importe quelle expression 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. Elles 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, des macros de type attribut 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 logiciels 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 personnalisées de type attribut 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 Custom function 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 variables statiques.

Voici comment définir une macro procédurale qui définit une macro dérivée personnalisée :

 use proc_macro::TokenStream;
use quote::quote;
use syn::{DeriveInput, parse_macro_input};

La macro utilisation importent les caisses et les types nécessaires à l’écriture d’une macro procédurale Rust.

 #[proc_macro_derive(MyTrait)]
pub fn my_derive_macro(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = &ast.ident;

    let gen = quote! {
        impl MyTrait for #name {
            // implementation here
        }
    };

    gen.into()
}

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’enum. La macro prend un TokenStream comme entrée contenant le code analysé dans un arbre syntaxique abstrait (AST) avec l’objet parse_macro_input ! macro.

La nom est l’identifiant 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 en méthode.

Pour utiliser la macro, vous devez l’importer à partir du module dans lequel vous l’avez déclarée :

 // assuming you declared the macro in a my_macro_module module

use my_macro_module::my_derive_macro;

Lors de la déclaration de la structure ou de l’enum qui utilise la macro, vous ajouterez l’élément #[derive(MyTrait)] au début de la déclaration.

 #[derive(MyTrait)]
struct MyStruct {
    // fields here
}

La déclaration de la structure avec l’attribut s’étend à une implémentation de l’attribut MonTrait Trait pour la structure :

 impl MyTrait for MyStruct {
    // implementation here
}

L’implémentation permet d’utiliser les méthodes de la structure MonTrait trait sur MyStruct instances.

Macros d’attributs

Les macros d’attributs sont des macros que vous pouvez appliquer à des é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 :

 // importing modules for the macro definition
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, AttributeArgs};

#[proc_macro_attribute]
pub fn my_attribute_macro(attr: TokenStream, item: TokenStream) -> TokenStream {
    let args = parse_macro_input!(attr as AttributeArgs);
    let input = parse_macro_input!(item as DeriveInput);
    let name = &input.ident;

    let gen = quote! {
        #input
        impl #name {
            // custom behavior here
        }
    };

    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 (analysé avec l’attribut parse_macro_input ! ) et l’élément (analysé avec la macro parse_macro_input ! ). Cette macro utilise la fonction quote ! pour générer le code, y compris l’élément d’entrée original et un élément d’entrée supplémentaire. 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() méthode.

Règles macro

Les règles de macro sont le type de macros le plus simple et le plus flexible. Elles 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 boilerplate afin d’abstraire les détails de bas niveau.

Voici comment définir et utiliser les macro-règles dans vos programmes Rust :

 macro_rules! make_vector {
    ( $( $x:expr ),* ) => {
        {
            let mut v = Vec::new();
            $(
                v.push($x);
            )*
            v
        }
    };
}

fn main() {
    let v = make_vector![1, 2, 3];
    println!("{:?}", v); // prints "[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 champ principal fonction.

À l’intérieur de la macro, la définition du motif correspond aux arguments transmis à la macro. La fonction $( $x:expr ),* La syntaxe correspond à toutes les expressions séparées par des virgules identifiées comme $x.

Les $( ) 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.

La sortie d'un programme qui imprime

Organisez efficacement vos projets de rouille

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.

Vous pouvez également organiser les programmes Rust en crates et en modules afin d’améliorer l’organisation du code, la réutilisation et l’interopérabilité avec d’autres crates et modules.