Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Les macros

Nous avons utilisé des macros comme println! tout au long de ce livre, mais nous n’avons pas exploré en détail ce qu’est une macro et comment elle fonctionne. Le terme macro fait référence à une famille de fonctionnalités en Rust – les macros déclaratives avec macro_rules! et trois types de macros procédurales :

  • Les macros #[derive] personnalisées qui spécifient du code ajouté avec l’attribut derive utilisé sur les structures et les enums
  • Les macros de type attribut qui définissent des attributs personnalisés utilisables sur n’importe quel élément
  • Les macros de type fonction qui ressemblent à des appels de fonction mais opèrent sur les tokens spécifiés comme arguments

Nous allons parler de chacune d’elles à tour de rôle, mais d’abord, voyons pourquoi nous avons même besoin de macros alors que nous avons déjà des fonctions.

La différence entre les macros et les fonctions

Fondamentalement, les macros sont un moyen d’écrire du code qui écrit d’autre code, ce qu’on appelle la métaprogrammation. Dans l’annexe C, nous discutons de l’attribut derive, qui génère une implémentation de divers traits pour vous. Nous avons aussi utilisé les macros println! et vec! tout au long du livre. Toutes ces macros se développent pour produire plus de code que le code que vous avez écrit manuellement.

La métaprogrammation est utile pour réduire la quantité de code que vous devez écrire et maintenir, ce qui est aussi l’un des rôles des fonctions. Cependant, les macros possèdent des pouvoirs supplémentaires que les fonctions n’ont pas.

Une signature de fonction doit déclarer le nombre et le type de paramètres de la fonction. Les macros, en revanche, peuvent prendre un nombre variable de paramètres : nous pouvons appeler println!("hello") avec un argument ou println!("hello {}", name) avec deux arguments. De plus, les macros sont développées avant que le compilateur n’interprète la signification du code, donc une macro peut, par exemple, implémenter un trait sur un type donné. Une fonction ne le peut pas, car elle est appelée à l’exécution et un trait doit être implémenté au moment de la compilation.

L’inconvénient d’implémenter une macro au lieu d’une fonction est que les définitions de macros sont plus complexes que les définitions de fonctions car vous écrivez du code Rust qui écrit du code Rust. En raison de cette indirection, les définitions de macros sont généralement plus difficiles à lire, comprendre et maintenir que les définitions de fonctions.

Une autre différence importante entre les macros et les fonctions est que vous devez définir les macros ou les importer dans la portée avant de les appeler dans un fichier, contrairement aux fonctions que vous pouvez définir n’importe où et appeler n’importe où.

Les macros déclaratives pour la métaprogrammation générale

La forme de macros la plus largement utilisée en Rust est la macro déclarative. Celles-ci sont aussi parfois appelées “macros par l’exemple”, “macros macro_rules!” ou simplement “macros”. En leur coeur, les macros déclaratives vous permettent d’écrire quelque chose de similaire à une expression match de Rust. Comme discuté au chapitre 6, les expressions match sont des structures de contrôle qui prennent une expression, comparent la valeur résultante de l’expression à des motifs, puis exécutent le code associé au motif correspondant. Les macros comparent aussi une valeur à des motifs associés à du code particulier : dans cette situation, la valeur est le code source Rust littéral passé à la macro ; les motifs sont comparés à la structure de ce code source ; et le code associé à chaque motif, lorsqu’il correspond, remplace le code passé à la macro. Tout cela se produit pendant la compilation.

Pour définir une macro, vous utilisez la construction macro_rules!. Explorons comment utiliser macro_rules! en regardant comment la macro vec! est définie. Le chapitre 8 a couvert comment nous pouvons utiliser la macro vec! pour créer un nouveau vecteur avec des valeurs particulières. Par exemple, la macro suivante crée un nouveau vecteur contenant trois entiers :

#![allow(unused)]
fn main() {
let v: Vec<u32> = vec![1, 2, 3];
}

Nous pourrions aussi utiliser la macro vec! pour créer un vecteur de deux entiers ou un vecteur de cinq slices de chaînes. Nous ne pourrions pas utiliser une fonction pour faire la même chose car nous ne connaîtrions pas le nombre ou le type de valeurs à l’avance.

L’encart 20-35 montre une définition légèrement simplifiée de la macro vec!.

Filename: src/lib.rs
#[macro_export]
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}
Listing 20-35: A simplified version of the vec! macro definition

Remarque : la définition réelle de la macro vec! dans la bibliothèque standard inclut du code pour pré-allouer la bonne quantité de mémoire à l’avance. Ce code est une optimisation que nous n’incluons pas ici, pour simplifier l’exemple.

L’annotation #[macro_export] indique que cette macro devrait être rendue disponible chaque fois que le crate dans lequel la macro est définie est importé dans la portée. Sans cette annotation, la macro ne peut pas être importée dans la portée.

Nous commençons ensuite la définition de la macro avec macro_rules! et le nom de la macro que nous définissons sans le point d’exclamation. Le nom, dans ce cas vec, est suivi d’accolades indiquant le corps de la définition de la macro.

La structure dans le corps de vec! est similaire à la structure d’une expression match. Ici nous avons une branche avec le motif ( $( $x:expr ),* ), suivi de => et du bloc de code associé à ce motif. Si le motif correspond, le bloc de code associé sera émis. Étant donné que c’est le seul motif dans cette macro, il n’y a qu’une seule manière valide de correspondre ; tout autre motif entraînera une erreur. Les macros plus complexes auront plus d’une branche.

La syntaxe de motifs valide dans les définitions de macros est différente de la syntaxe de motifs couverte au chapitre 19 car les motifs de macros sont comparés à la structure du code Rust plutôt qu’à des valeurs. Parcourons ce que signifient les éléments du motif dans l’encart 20-29 ; pour la syntaxe complète des motifs de macros, consultez la [Référence Rust][ref].

D’abord, nous utilisons un jeu de parenthèses pour englober tout le motif. Nous utilisons un signé dollar ($) pour déclarer une variable dans le système de macros qui contiendra le code Rust correspondant au motif. Le signé dollar indique clairement qu’il s’agit d’une variable de macro par opposition à une variable Rust normale. Ensuite vient un jeu de parenthèses qui capture les valeurs correspondant au motif à l’intérieur des parenthèses pour utilisation dans le code de remplacement. À l’intérieur de $() se trouve $x:expr, qui correspond à n’importe quelle expression Rust et donne à l’expression le nom $x.

La virgule suivant $() indique qu’un caractère séparateur virgule littéral doit apparaître entre chaque instance du code correspondant au code dans $(). Le * spécifie que le motif correspond à zéro ou plusieurs occurrences de ce qui précède le *.

Lorsque nous appelons cette macro avec vec![1, 2, 3];, le motif $x correspond trois fois avec les trois expressions 1, 2 et 3.

Maintenant regardons le motif dans le corps du code associé à cette branche : temp_vec.push() à l’intérieur de $()* est généré pour chaque partie qui correspond à $() dans le motif zéro ou plusieurs fois selon le nombre de fois que le motif correspond. Le $x est remplacé par chaque expression correspondante. Lorsque nous appelons cette macro avec vec![1, 2, 3];, le code généré qui remplace cet appel de macro sera le suivant :

{
    let mut temp_vec = Vec::new();
    temp_vec.push(1);
    temp_vec.push(2);
    temp_vec.push(3);
    temp_vec
}

Nous avons défini une macro qui peut prendre n’importe quel nombre d’arguments de n’importe quel type et peut générer du code pour créer un vecteur contenant les éléments spécifiés.

Pour en apprendre plus sur l’écriture de macros, consultez la documentation en ligne ou d’autres ressources, comme [“The Little Book of Rust Macros”][tlborm] commencé par Daniel Keep et continué par Lukas Wirth.

Les macros procédurales pour générer du code à partir d’attributs

La deuxième forme de macros est la macro procédurale, qui agit davantage comme une fonction (et est un type de procédure). Les macros procédurales acceptent du code en entrée, opèrent sur ce code et produisent du code en sortie plutôt que de faire correspondre des motifs et de remplacer le code par un autre code comme le font les macros déclaratives. Les trois types de macros procédurales sont les macros derive personnalisées, les macros de type attribut et les macros de type fonction, et toutes fonctionnent de manière similaire.

Lors de la création de macros procédurales, les définitions doivent résider dans leur propre crate avec un type de crate spécial. C’est pour des raisons techniques complexes que nous espérons éliminer à l’avenir. Dans l’encart 20-36, nous montrons comment définir une macro procédurale, où some_attribute est un espace réservé pour l’utilisation d’une variété spécifique de macro.

Filename: src/lib.rs
use proc_macro::TokenStream;

#[some_attribute]
pub fn some_name(input: TokenStream) -> TokenStream {
}
Listing 20-36: An example of defining a procedural macro

La fonction qui définit une macro procédurale prend un TokenStream en entrée et produit un TokenStream en sortie. Le type TokenStream est défini par le crate proc_macro inclus avec Rust et représente une séquence de tokens. C’est le coeur de la macro : le code source sur lequel la macro opère constitue le TokenStream d’entrée, et le code que la macro produit est le TokenStream de sortie. La fonction a aussi un attribut attaché qui spécifie quel type de macro procédurale nous créons. Nous pouvons avoir plusieurs types de macros procédurales dans le même crate.

Regardons les différents types de macros procédurales. Nous commencerons par une macro derive personnalisée puis expliquerons les petites différences qui distinguent les autres formes.

Les macros derive personnalisées

Créons un crate nommé hello_macro qui définit un trait nommé HelloMacro avec une fonction associée nommée hello_macro. Plutôt que de demander à nos utilisateurs d’implémenter le trait HelloMacro pour chacun de leurs types, nous fournirons une macro procédurale pour que les utilisateurs puissent annoter leur type avec #[derive(HelloMacro)] pour obtenir une implémentation par défaut de la fonction hello_macro. L’implémentation par défaut affichera Hello, Macro! My name is TypeName!TypeName est le nom du type sur lequel ce trait a été défini. En d’autres termes, nous écrirons un crate qui permettra à un autre programmeur d’écrire du code comme l’encart 20-37 en utilisant notre crate.

Filename: src/main.rs
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;

#[derive(HelloMacro)]
struct Pancakes;

fn main() {
    Pancakes::hello_macro();
}
Listing 20-37: The code a user of our crate will be able to write when using our procedural macro

Ce code affichera Hello, Macro! My name is Pancakes! lorsque nous aurons terminé. La première étape est de créer un nouveau crate de bibliothèque, comme ceci :

$ cargo new hello_macro --lib

Ensuite, dans l’encart 20-38, nous définirons le trait HelloMacro et sa fonction associée.

Filename: src/lib.rs
pub trait HelloMacro {
    fn hello_macro();
}
Listing 20-38: A simple trait that we will use with the derive macro

Nous avons un trait et sa fonction. À ce stade, l’utilisateur de notre crate pourrait implémenter le trait pour obtenir la fonctionnalité souhaitée, comme dans l’encart 20-39.

Filename: src/main.rs
use hello_macro::HelloMacro;

struct Pancakes;

impl HelloMacro for Pancakes {
    fn hello_macro() {
        println!("Hello, Macro! My name is Pancakes!");
    }
}

fn main() {
    Pancakes::hello_macro();
}
Listing 20-39: How it would look if users wrote a manual implementation of the HelloMacro trait

Cependant, ils devraient écrire le bloc d’implémentation pour chaque type qu’ils voudraient utiliser avec hello_macro ; nous voulons leur épargner ce travail.

De plus, nous ne pouvons pas encore fournir à la fonction hello_macro une implémentation par défaut qui afficherait le nom du type sur lequel le trait est implémenté : Rust n’a pas de capacités de réflexion, il ne peut donc pas rechercher le nom du type à l’exécution. Nous avons besoin d’une macro pour générer du code au moment de la compilation.

L’étape suivante est de définir la macro procédurale. Au moment de l’écriture, les macros procédurales doivent être dans leur propre crate. À terme, cette restriction pourrait être levée. La convention pour structurer les crates et les crates de macros est la suivante : pour un crate nommé foo, un crate de macro procédurale derive personnalisée s’appelle foo_derive. Commençons un nouveau crate appelé hello_macro_derive dans notre projet hello_macro :

$ cargo new hello_macro_derive --lib

Nos deux crates sont étroitement liés, nous créons donc le crate de macro procédurale dans le répertoire de notre crate hello_macro. Si nous changeons la définition du trait dans hello_macro, nous devrons aussi changer l’implémentation de la macro procédurale dans hello_macro_derive. Les deux crates devront être publiés séparément, et les programmeurs utilisant ces crates devront ajouter les deux comme dépendances et les importer tous les deux dans la portée. Nous pourrions plutôt faire en sorte que le crate hello_macro utilise hello_macro_derive comme dépendance et ré-exporte le code de la macro procédurale. Cependant, la manière dont nous avons structuré le projet permet aux programmeurs d’utiliser hello_macro même s’ils ne veulent pas la fonctionnalité derive.

Nous devons déclarer le crate hello_macro_derive comme un crate de macro procédurale. Nous aurons aussi besoin des fonctionnalités des crates syn et quote, comme vous le verrez dans un instant, nous devons donc les ajouter comme dépendances. Ajoutez ce qui suit au fichier Cargo.toml de hello_macro_derive :

Filename: hello_macro_derive/Cargo.toml
[lib]
proc-macro = true

[dependencies]
syn = "2.0"
quote = "1.0"

Pour commencer à définir la macro procédurale, placez le code de l’encart 20-40 dans votre fichier src/lib.rs du crate hello_macro_derive. Notez que ce code ne compilera pas tant que nous n’ajoutons pas une définition pour la fonction impl_hello_macro.

Filename: hello_macro_derive/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    // Construct a représentation of Rust code as a syntax tree
    // that we can manipulate.
    let ast = syn::parse(input).unwrap();

    // Build the trait implémentation.
    impl_hello_macro(&ast)
}
Listing 20-40: Code that most procedural macro crates will require in order to process Rust code

Remarquez que nous avons séparé le code en la fonction hello_macro_derive, qui est responsable de l’analyse du TokenStream, et la fonction impl_hello_macro, qui est responsable de la transformation de l’arbre syntaxique : cela rend l’écriture d’une macro procédurale plus pratique. Le code dans la fonction externe (hello_macro_derive dans ce cas) sera le même pour presque chaque crate de macro procédurale que vous verrez ou créerez. Le code que vous spécifiez dans le corps de la fonction interne (impl_hello_macro dans ce cas) sera différent selon l’objectif de votre macro procédurale.

Nous avons introduit trois nouvelles crates{N}: proc_macro, syn et quote. La crate proc_macro est livrée avec Rust, nous n’avons donc pas eu besoin de l’ajouter aux dépendances dans Cargo.toml. La crate proc_macro est l’API du compilateur qui nous permet de lire et de manipuler du code Rust depuis notre code.

Le crate syn analyse le code Rust depuis une chaîne de caractères vers une structure de données sur laquelle nous pouvons effectuer des opérations. Le crate quote retransforme les structures de données syn en code Rust. Ces crates rendent beaucoup plus simple l’analyse de tout type de code Rust que nous pourrions vouloir traiter : écrire un analyseur complet pour le code Rust n’est pas une tâche simple.

La fonction hello_macro_derive sera appelée lorsqu’un utilisateur de notre bibliothèque spécifie #[derive(HelloMacro)] sur un type. C’est possible car nous avons annoté la fonction hello_macro_derive ici avec proc_macro_derive et spécifié le nom HelloMacro, qui correspond au nom de notre trait ; c’est la convention que la plupart des macros procédurales suivent.

La fonction hello_macro_derive convertit d’abord l’input d’un TokenStream vers une structure de données que nous pouvons ensuite interpréter et sur laquelle nous pouvons effectuer des opérations. C’est là que syn entre en jeu. La fonction parse de syn prend un TokenStream et retourné une structure DeriveInput représentant le code Rust analysé. L’encart 20-41 montre les parties pertinentes de la structure DeriveInput que nous obtenons de l’analyse de la chaîne struct Pancakes;.

DeriveInput {
    // --snip--

    ident: Ident {
        ident: "Pancakes",
        span: #0 bytes(95..103)
    },
    data: Struct(
        DataStruct {
            struct_token: Struct,
            fields: Unit,
            semi_token: Some(
                Semi
            )
        }
    )
}
Listing 20-41: The DeriveInput instance we get when parsing the code that has the macro’s attribute in Listing 20-37

Les champs de cette structure montrent que le code Rust que nous avons analysé est une structure unitaire avec l’ident (identifiant, c’est-à-dire le nom) Pancakes. Il y a d’autres champs sur cette structure pour décrire toutes sortes de code Rust ; consultez la [documentation de syn pour DeriveInput][syn-docs] pour plus d’informations.

Bientôt nous définirons la fonction impl_hello_macro, qui est l’endroit où nous construirons le nouveau code Rust que nous voulons inclure. Mais avant cela, notez que la sortie de notre macro derive est aussi un TokenStream. Le TokenStream retourné est ajouté au code que les utilisateurs de notre crate écrivent, donc lorsqu’ils compilent leur crate, ils obtiendront la fonctionnalité supplémentaire que nous fournissons dans le TokenStream modifié.

Vous avez peut-être remarqué que nous appelons unwrap pour faire paniquer la fonction hello_macro_derive si l’appel à la fonction syn::parse échoue ici. Il est nécessaire que notre macro procédurale panique en cas d’erreur car les fonctions proc_macro_derive doivent retourner un TokenStream plutôt qu’un Result pour se conformer à l’API des macros procédurales. Nous avons simplifié cet exemple en utilisant unwrap ; dans du code de production, vous devriez fournir des messages d’erreur plus spécifiques sur ce qui s’est mal passé en utilisant panic! ou expect.

Maintenant que nous avons le code pour transformer le code Rust annoté d’un TokenStream en une instance DeriveInput, générons le code qui implémente le trait HelloMacro sur le type annoté, comme montré dans l’encart 20-42.

Filename: hello_macro_derive/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    // Construct a représentation of Rust code as a syntax tree
    // that we can manipulate
    let ast = syn::parse(input).unwrap();

    // Build the trait implémentation
    impl_hello_macro(&ast)
}

fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;
    let generated = quote! {
        impl HelloMacro for #name {
            fn hello_macro() {
                println!("Hello, Macro! My name is {}!", stringify!(#name));
            }
        }
    };
    generated.into()
}
Listing 20-42: Implementing the HelloMacro trait using the parsed Rust code

Nous obtenons une instance de la structure Ident contenant le nom (identifiant) du type annoté en utilisant ast.ident. La structure dans l’encart 20-41 montre que lorsque nous exécutons la fonction impl_hello_macro sur le code de l’encart 20-37, l’ident que nous obtenons aura le champ ident avec une valeur de "Pancakes". Ainsi, la variable name dans l’encart 20-42 contiendra une instance de la structure Ident qui, lorsqu’elle sera affichée, sera la chaîne "Pancakes", le nom de la structure dans l’encart 20-37.

La macro quote! nous permet de définir le code Rust que nous voulons retourner. Le compilateur attend quelque chose de différent du résultat direct de l’exécution de la macro quote!, nous devons donc le convertir en un TokenStream. Nous faisons cela en appelant la méthode into, qui consomme cette représentation intermédiaire et retourné une valeur du type TokenStream requis.

La macro quote! fournit aussi des mécanismes de modèles très pratiques : nous pouvons entrer #name, et quote! le remplacera par la valeur de la variable name. Vous pouvez même faire de la répétition de manière similaire au fonctionnement des macros normales. Consultez [la documentation du crate quote][quote-docs] pour une introduction approfondie.

Nous voulons que notre macro procédurale génère une implémentation de notre trait HelloMacro pour le type que l’utilisateur a annoté, que nous pouvons obtenir en utilisant #name. L’implémentation du trait à une seule fonction hello_macro, dont le corps contient la fonctionnalité que nous voulons fournir : afficher Hello, Macro! My name is suivi du nom du type annoté.

La macro stringify! utilisée ici est intégrée à Rust. Elle prend une expression Rust, comme 1 + 2, et au moment de la compilation transformé l’expression en un littéral de chaîne, comme "1 + 2". C’est différent de format! ou println!, qui sont des macros qui évaluent l’expression puis transforment le résultat en une String. Il est possible que l’entrée #name soit une expression à afficher littéralement, c’est pourquoi nous utilisons stringify!. L’utilisation de stringify! économise aussi une allocation en convertissant #name en un littéral de chaîne au moment de la compilation.

À ce stade, cargo build devrait se terminer avec succès dans hello_macro et hello_macro_derive. Connectons ces crates au code de l’encart 20-37 pour voir la macro procédurale en action ! Créez un nouveau projet binaire dans votre répertoire projects en utilisant cargo new pancakes. Nous devons ajouter hello_macro et hello_macro_derive comme dépendances dans le Cargo.toml du crate pancakes. Si vous publiez vos versions de hello_macro et hello_macro_derive sur crates.io, ce seraient des dépendances normales ; sinon, vous pouvez les spécifier comme dépendances path comme suit : toml {{#include ../listings/ch20-advanced-features/no-listing-21-pancakes/pancakes/Cargo.toml:6:8}}

[dependencies]
hello_macro = { path = "../hello_macro" }
hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }

Placez le code de l’encart 20-37 dans src/main.rs et exécutez cargo run : il devrait afficher Hello, Macro! My name is Pancakes!. L’implémentation du trait HelloMacro par la macro procédurale a été incluse sans que le crate pancakes n’ait besoin de l’implémenter ; le #[derive(HelloMacro)] a ajouté l’implémentation du trait.

Ensuite, explorons en quoi les autres types de macros procédurales diffèrent des macros derive personnalisées.

Les macros de type attribut

Les macros de type attribut sont similaires aux macros derive personnalisées, mais au lieu de générer du code pour l’attribut derive, elles vous permettent de créer de nouveaux attributs. Elles sont aussi plus flexibles : derive ne fonctionne que pour les structures et les enums ; les attributs peuvent être appliqués à d’autres éléments aussi, comme les fonctions. Voici un exemple d’utilisation d’une macro de type attribut. Imaginons que vous ayez un attribut nommé route qui annote des fonctions lors de l’utilisation d’un framework d’application web :

#[route(GET, "/")]
fn index() {

Cet attribut #[route] serait défini par le framework comme une macro procédurale. La signature de la fonction de définition de la macro ressemblerait à ceci :

#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {

Ici, nous avons deux paramètres de type TokenStream. Le premier est pour le contenu de l’attribut : la partie GET, "/". Le second est le corps de l’élément auquel l’attribut est attaché : dans ce cas, fn index() {} et le reste du corps de la fonction.

À part cela, les macros de type attribut fonctionnent de la même manière que les macros derive personnalisées : vous créez un crate avec le type de crate proc-macro et implémentez une fonction qui génère le code que vous voulez !

Les macros de type fonction

Les macros de type fonction définissent des macros qui ressemblent à des appels de fonction. Comme les macros macro_rules!, elles sont plus flexibles que les fonctions ; par exemple, elles peuvent prendre un nombre inconnu d’arguments. Cependant, les macros macro_rules! ne peuvent être définies qu’en utilisant la syntaxe de type match que nous avons discutée dans la section [“Les macros déclaratives pour la métaprogrammation générale”][decl] plus tôt. Les macros de type fonction prennent un paramètre TokenStream, et leur définition manipule ce TokenStream en utilisant du code Rust comme le font les deux autres types de macros procédurales. Un exemple de macro de type fonction est une macro sql! qui pourrait être appelée comme suit :

let sql = sql!(SELECT * FROM posts WHERE id=1);

Cette macro analyserait l’instruction SQL à l’intérieur et vérifierait qu’elle est syntaxiquement correcte, ce qui est un traitement beaucoup plus complexe que ce qu’une macro macro_rules! peut faire. La macro sql! serait définie comme ceci :

#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {

Cette définition est similaire à la signature de la macro derive personnalisée : nous recevons les tokens qui sont à l’intérieur des parenthèses et retournons le code que nous voulions générer.

Résumé

Ouf ! Vous avez maintenant quelques fonctionnalités Rust dans votre boîte à outils que vous n’utiliserez probablement pas souvent, mais vous saurez qu’elles sont disponibles dans des circonstances très particulières. Nous avons introduit plusieurs sujets complexes afin que lorsque vous les rencontrez dans les suggestions de messages d’erreur ou dans le code d’autres personnes, vous puissiez reconnaître ces concepts et cette syntaxe. Utilisez ce chapitre comme référence pour vous guider vers des solutions.

Ensuite, nous mettrons en pratique tout ce que nous avons discuté tout au long du livre et réaliserons un dernier projet !