Les macros en Rust

Lecture 13 min. ‱


Table des matiĂšres

Bonjour à toutes et tous 😀

Le rust est un formidable langage mais du fait de son besoin d’explicitĂ©, il est parfois fastidueux d’écrire tout le code nĂ©cessaire.

Les macros sont un systĂšme qui se place au-dessus de la compilation et vient gĂ©nĂ©rer du code pour Ă©viter au dĂ©veloppeur de l’écrire.

Il existe 2 types de macros:

L’article d’aujourd’hui parlera exclusivement des macro_rules.

J’écris cet article sous la forme d’un pense-bĂȘte pour mon futur, mais je vous en fait aussi cadeau ! đŸ€—

Les bases des macros

Avant de se lancer à corps perdu dans la bataille ! ⚔

Quelques explications s’imposent.

D’abord, comment marche une macro ?

Une macro est une routine qui a pour rĂŽle d’écrire du code qui sera ensuite compilĂ©.

Prenons un exemple classique, celui d’un hello world.

fn main() {
    println!("hello world");
}

Nous pouvons remplacer ce code par une macro qui va l’écrire Ă  notre place.

macro_rules! hello {
    () => {
        println!("hello world");
    };
}

fn main() {
    hello!();
}

Ces deux codes rĂ©alisent exactement la mĂȘme chose.

La différence fondamentale est que le deuxiÚme code génÚre le premier.

L’appel de la macro se fait au moyen du hello!() le ! est ici capital, il permet de distinguer l’appel d’une fonction classique de celui d’une macro.

Remarque

println!("hello world") est Ă©galement une macro. 😁

Lorsque vous réalisé un cargo build ou cargo run, une étape de pré-compilation est réalisée, celle-ci vient remplacer le code de la macro par son résultat étendu.

  


flowchart LR
    a(Code avec macro)
    b(Code précompilé)
    a-->|pré compilation|b
    b-->|compilation|Executable


Ici, la macro hello()! s’étend en println!("hello world") qui lui mĂȘme sera Ă©tendu vers du code qui peut-ĂȘtre compilĂ©.

Ce n’est qu’une fois toutes les macros Ă©tendues que le compilateur va rĂ©ellement construire notre Ă©xĂ©cutable.

Les macros peuvent avoir des paramĂštres

Remarquez à la ligne 2, le () => {, ça ressemblerait presque de loin à une alternative de match Rust non ?

macro_rules! hello {
    () => {
        println!("hello world");
    };
}

Et pour cause c’est un match, il match la signature de ce qui lui est transmis.

Cette ligne compilera

hello!()

Pas celle-lĂ 

hello!(1)
1 | macro_rules! hello {
  | ------------------ when calling this macro
...
9 |     hello!(1);
  |            ^ no rules expected this token in macro call

Pour le refaire fonctionner on a plusieurs choix.

Le premier est de faire coincider le pattern avec l’entrĂ©e

macro_rules! hello {
    (1) => {
        println!("hello world");
    };
}

Ceci compile mais n’est pas trùs souple car:

hello!(2)

Ne compilera pas.

Si on veut que les 3 appels compilent

hello!();
hello!(1);
hello!(2);

Nous pouvons faire quelque chose comme ceci:

macro_rules! hello {
    () => {
        println!("hello world");
    };
    (1) => {
        println!("hello world");
    };
    (2) => {
        println!("hello world");
    };
}

Bon c’est cool mais on a pas un truc un peux plus souple que ça ?? Je vais pas rajouter tous les nombres de la Terre non plus
 😡

BiensĂ»r qu’il existe un moyen. Il est possible varibiliser 😀

macro_rules! hello {
    () => {
        println!("hello world");
    };
    ($nb:expr) => {
        println!("hello world");
    };
}

Grùce à ça on peut utiliser notre macro avec nos 3 hello mais aussi des truc plus exotique:

hello!();
hello!(1);
hello!(-45258928);
hello!("je suis lĂ ");

Nous n’avons pas de notion de typage, on match une expression pas une variable.

C’est un peu compliquĂ© Ă  comprendre au dĂ©but mais il faut non pas se mettre du point de vue du dĂ©veloppeur mais bien du compilateur !

Ici notre macro ne fait que vĂ©rifier s’il y a bien une expression entre parenthĂšses. Et en Rust une expression peut-ĂȘtre bien des choses. 😅

Maintenant un exemple un peu plus intéressant, faire la somme de plusieurs nombres.

macro_rules! sum {
    () => {
        0
    };
    ($x:expr) => {
      $x
    };
    ($x:expr, $y:expr) => {
        $x + $y
    }
}

fn main() {
    println!("{}", sum!());
    println!("{}", sum!(6));
    println!("{}", sum!(6, 7));
}

Expliquons un peu ce code,

Tout d’abord

    ($x:expr) => {
      $x
    };

Cette variante de notre macro, prend en paramÚtre une entrée $x et vient recopier ce contenu dans sa sortie. Le code étendu sera donc notre entrée.

La deuxiĂšme variant de notre macro est:

    ($x:expr, $y:expr) => {
        $x + $y
    }

Elle prend deux paramÚtres $x et $y en entrées.

Ce qui signifie que la macro est capable de comprendre des entrées mutiples.

Et pour la somme de 3 nombres ?

Code Rust

Facile non ?

macro_rules! sum {
    () => {
        0
    };
    ($x:expr) => {
      $x
    };
    ($x:expr, $y:expr) => {
        $x + $y
    };
    ($x:expr, $y:expr , $z:expr) => {
        $x + $y + $z
    }
}

fn main() {
    println!("{}", sum!());
    println!("{}", sum!(6));
    println!("{}", sum!(6 , 7));
    println!("{}", sum!(6 , 7 , 4));
}

Ok et pour 5, 10, 20, n opérandes dans ta somme ?

Pour le moment on ne peut pas, il nous manque certains concepts.

Mais ne vous inquiĂ©tez pas, nous allons les passer en revue. 😀

Répétition

Imaginez que vous vous vouliez rajouter 2 Ă  chacun des Ă©lĂ©ments d’un Vec<u8>.

Ce qui signifie que si initialement nous avons

vec![1, 3, 5];

alors l’appel de la macro doit produire:

vec![3, 5, 7];

Comment Ă©crire la macro pour que l’on puisse avoir un nombre arbitraire d’élements dans le tableau ?

Nous pouvons écrire la macro ainsi:

macro_rules! vec_plus_2 {
    (vec![$($x:expr),*]) => {
        vec![$($x + 2),*]
    };
}

fn main() {

    println!("{:?}", vec_plus_2!(vec![1, 3, 5]));
}

Nous affiche:

[3, 5, 7]

Analysons un peu ce qui se passe lors de l’éxĂ©cution de la macro.

Tout d’abord notre pattern est composĂ© de (vec![...]), ce qu’il signifie qu’il s’attend Ă  avoir un vec! de quelque chose en entrĂ©e.

Ensuite nous avons une syntaxe un peu spĂ©ciale, $(...),*, celle ci s’appelle une rĂ©pĂ©tition.

Si on décompose cela nous donne $(...) sep rep

Le sĂ©parateur peut-ĂȘtre , ou ;.

Il existe 3 types de répétitions rep différentes:

Ainsi, le pattern $($x:expr),* match zéro ou plusieurs fois des expressions séparées par un virgule ,.

Notre macro va avoir pour entrée vec![1, 3, 5].

Le corps de notre macro est:

vec![$($x + 2),*]

DĂ©composons ce qu’il se passe:

  1. vec![ : on écrit les caractÚres tels quels
  2. on rentre dans la répétition
  3. le $x match l’expression 1 : on Ă©crit vec![1 + 2
  4. un séparateur , est rajouté vec![1 + 2,
  5. le $x match l’expression 3 : on Ă©crit vec![1 + 2, 3 + 2
  6. un séparateur , est rajouté vec![1 + 2, 3 + 2,
  7. le $x match l’expression 5 : on Ă©crit vec![1 + 2, 3 + 2, 5 + 2
  8. il n’y plus d’expression Ă  matcher, on sort de la rĂ©pĂ©tition
  9. le ] est rajouté : vec![1 + 2, 3 + 2, 5 + 2]

Finalement nous avons notre sortie, qui une fois éxécutée donnera notre résultat du println!

Précision

Mais il faut bien garder en tĂȘte que le code compilĂ© sera

println!(vec![1 + 2, 3 + 2, 5 + 2]) 

et non

println!(vec![3, 5, 7])

Attraper les tous !

Les macros peuvent capturer tout un tas de choses, Ă  vrai dire tout peut ĂȘtre capturĂ©.

Non, ce ne sont pas des pokĂ©mons comme le titre le laisse prĂ©sager, il s’agit de ce que l’on appelle des mĂ©tavariables.

Il en existe un total de 11 différentes.

Item

Les items sont reprĂ©sentĂ©s par l’identifieur $x:item.

Contrairement à ce que l’on pourrait penser, il ne match pas un item de vec! par exemple, mais en fait tout ce qui existe dans un module, les modules interne compris.

La liste complĂšte des choses qui peuvent-ĂȘtre capturĂ© est ici.

macro_rules! item_match {
    ($mod:item) => {
        fn toto() {
            println!("Hello");
        }
    };
}

item_match! {
    mod toto {
        fn tata() {
            println!("Bye!")
        }
    }
}

Une fois étendu le code donnera:

fn toto() {
    println!("Hello");
}

On utilise le type item pour effectuer des remplacements brutaux dans le code. Et on peut littĂ©ralement tout matcher avec! 😁

Block

Les blocks sont reprĂ©sentĂ©s par l’identifieur $x:block.

On se spécialise un peu plus, au lieu de tout matcher on va uniquement pouvoir matcher des blocs de code.

Ceux-ci sont délimités par des { et }.

Exemple:

macro_rules! block_match {
    ($block:block) => {
        println!("666")
    };
}

fn main() {
    block_match!{
        {
            println!("42");
        }
    }
}

Au lieu d’afficher 42, nous allons remplacer le bloc de code par println!("666") et donc afficher Ă  l’éxĂ©cution 666.

Statement

Les statements sont reprĂ©sentĂ©s par l’identifieur $x:stmt.

Ils matchent toutes les expressions se terminant par un point-virgule.

Exemple:

macro_rules! statements_match {
    ({$($statement:stmt);*$(;)*}) => {
        fn wrapper() {
            $($statement);*;
        }
    };
}

fn main() {
    statements_match!({
        println!("tata");
        println!("titi");
        println!("tutu");
        let x = 2;
        println!("x = {}", x);
    });

    wrapper();
}

Cela va crĂ©er une fonction wrapper qui lorsqu’elle est appelĂ©e affichera:

tata
titi
tutu
x = 2

Ici on bénéficie aussi de la répétition pour pouvoir matcher tous les statements du bloc.

Expression

Les expressions sont reprĂ©sentĂ©es par l’identifieur $x:expr.

Les expressions sont tout les éléments qui produisent un résultat, la liste complÚte est disponible ici.

Les expressions rassemblent ce qui peut-ĂȘtre affectĂ© Ă  une variable via la syntaxe let ou let mut.

Mais il est aussi possible de matcher des appels de fonctions sans pour autant devoir affecter le résultat à une variable.

macro_rules! expression_match {
    ({$(let $x:ident = $expression:expr;)*}) => {
        fn block_wrapper() {
            $(let $x = $expression + 1);*;
        }
    };
}

fn main() {
    expression_match! {
        {
            let x = 1;
            let y = {
              5
            };
        }
    }
}

Vient écrire une fois étendue:

fn block_wrapper() {
    let x = 1 + 1;
    let y = ({
        5
    }) + 1;
}

On peut aussi faire un exemple sans affectation de variable.

macro_rules! expression_match {
    ({$($expression:expr);*}) => {
        fn block_wrapper2() {
            $($expression);*;
        }
    };
}

fn main() {

    fn hello3() {
        println!("hello3")
    }

    expression_match! {
        {
            hello3()
        }
    }
    block_wrapper2()
}

Ce code affichera hello3.

Identifier

Les identifiers sont reprĂ©sentĂ©s par l’identifieur $x:ident.

Ceux-ci correspondent principalement aux noms donnés aux choses manipulées dans le code (variable, structure, enum, champ de structure, 
).

macro_rules! identifier_match {
    (fn $name:ident {
        $($line:stmt)*
    }) => {
        fn $name () -> u8 {
            $($line)*
            println!("fin {}", stringify!($name));
            42
        }
    };
}

fn main() {
        identifier_match!(fn answer_to_everything {
        let x = 3;
        let y = 42;
    });
    println!("answer: {}", answer_to_everything());
}

Ce code créé une fonction answer_to_everything qui affiche fin answer_to_everything et renvoie 42;

fn answer_to_everything() -> u8 {
        let x = 3;
        let y = 42;

        println!("fin {}", stringify!(answer_to_everything));
        42
}

On capture le nom de la fonction pour l’utiliser dans la suite de la macro.

La macro stringify! permet de transformer un ident en string et ainsi pouvoir l’afficher dans un println!

Pattern

Les patterns sont reprĂ©sentĂ©s par l’identifieur $x:pat.

Ils peuvent matcher toutes les ranges possibles.

Exemple:

macro_rules! pattern_match {
    ($pattern:pat) => {
        0..=7
    };
}

fn main() {
    for i in pattern_match!(0..666) {
        println!("{}", i)
    }
}

Au lieu d’afficher les nombres de 0 à 665, on va afficher les nombres de 0 à 7 compris.

On peut aussi venir matcher des pattern dans des opérations if let ou des match.

Par exemple:

macro_rules! if_let_match {
    (if let $pat:pat = $ident:ident {}) => {
        println!("Le pattern du if let est {}", stringify!($pat))
    };
}

fn main() {

    if_let_match! {
        if let Ok(Some(x)) = toto {}
    }

    if_let_match! {
        if let Err(None) = toto {}
    }
}

Affiche:

Le pattern du if let est Ok(Some(x))
Le pattern du if let est Err(None)

Type

Les types sont reprĂ©sentĂ©s par l’identifieur $x:ty.

Comme son nom l’indique cette mĂ©tavariable vient matcher les types des expressions.

Par exemple

macro_rules! type_match {
    (($($type:ty),*)) => {
        vec![$(stringify!($type)),*]
    };
}

fn main() {

    struct A;

    let types = type_match! {
        (u8, f32, A)
    };
    for t in types {
        println!("{}", t)
    }
}

Affiche:

u8
f32
A

Et comme maintenant vous ĂȘtes aussi capables de comprendre les ident, on peut s’amuser avec les champs des structures.

macro_rules! struct_field_name_type_match {
    (struct $name:ident  {
        $($field_name:ident:$field_type:ty),*
    }
    ) => {
        println!("Le nom de la struct est {}", stringify!($name));
        $(println!("\ta pour champ {} de type {}", stringify!($field_name), stringify!($field_type)));*;
    };
}

fn main() {
    
    struct A;
    
    struct_field_name_type_match! {
        struct Person {
            name: String,
            age: u8,
            address: A
        }
    }
}

Affiche

Le nom de la struct est Person
	a pour champ name de type String
	a pour champ age de type u8
	a pour champ address de type A

Chemin

Les paths sont reprĂ©sentĂ©s par l’identifieur $x:path.

Correspondent au chaines de caractÚres représentant les chemins vers les modules ou les objets importés.

Ceux-ci sont séparés par des ::.

macro_rules! path_match {
    (mod $path:path) => {
        println!("Le nom du module est {}", stringify!($path))
    };
}

fn main() {
    path_match! {
        mod path::test::toto::tutu
    }
}

Affiche:

Le nom du module est path::test::toto::tutu

Token Tree

Les token tree sont reprĂ©sentĂ©s par l’identifieur $x:tt.

Ils permettent de matcher tout ce qui peut se trouver entre:

Je dois vous avouer que j’ai essayĂ© de comprendre comment ça marche mais je n’ai pas rĂ©ussi Ă  comprendre. 😅

La doc est plutÎt énigmatique.

Elle parle de matcher des tokens sĂ©parĂ©s par des dĂ©limiteurs, mais avec mes tests je n’ai pas d’exemple probant Ă  vous montrer pour une fois. 😛

Litteral

Les literals sont reprĂ©sentĂ©s par l’identifieur $x:literal.

Cela correspond aux valeurs primitives affectées aux variables et constantes.

macro_rules! literal_match {
    ($lit:literal) => {
        println!("{} est un literal", $lit)
    };
    ($x:ident) => {
       println!("{} est un ident", stringify!($x))
    }
}

fn main() {
   literal_match! {
        5
    }

    literal_match! {
        5.2_f32
    }

    literal_match! {
        "chat"
    }

    literal_match! {
        bool
    }

    literal_match! {
        x
    } 
} 

Ce code affichera:

5 est un literal
5.2 est un literal
chat est un literal
bool est un ident
x est un ident

Lifetime

Les lifetimes sont reprĂ©sentĂ©s par l’identifieur $x:lifetime.

Il permettent de matcher les lifetime utilisé par le borrow checker.

macro_rules! lifetime_match {
    (fn $name:ident<$($lifetime:lifetime),*>($($_param:ident : $_type:ty),*){$($_line:stmt);*}) => {
        println!("la fonction {}", stringify!($name));
        $(println!("\ta pour lifetime {}", stringify!($lifetime)));*
    };
}

fn main() {
    lifetime_match! {
        fn test<'a, 'b>(x: u8, y : bool) {
            println!("coucou")
        }
    }
}

Affiche

la fonction test
	a pour lifetime 'a
	a pour lifetime 'b

Visibilité

Les visilities sont reprĂ©sentĂ©es par l’identifieur $x:vis.

Les visibilitĂ©s sont les mots-clef permettant de spĂ©cifier la portĂ©e des Ă©lĂ©ments exportables d’un module Ă  l’autre.

Il existe 5 types de visibilités différentes.

Il est possible de matcher toutes ces visibilités.

macro_rules! visibility_match {
    ($visibility:vis fn $name:ident($($_param:ident : $_type:ty),*){$($_line:stmt);*}) => {
        println!("la fonction {} a pour visibilité {}", stringify!($name), stringify!($visibility))
    };
}

fn main() {
    
    visibility_match! {
        fn test_private() {}
    }

    visibility_match! {
        pub fn test_public() {}
    }

    visibility_match! {
        pub (self) fn test_self() {}
    }

    visibility_match! {
        pub (super) fn test_super() {}
    }

    visibility_match! {
        pub (crate) fn test_crate() {}
    }

    visibility_match! {
        pub (in module::mod2::test) fn test_in_path() {}
    }
}

Affiche:

la fonction test_private a pour visibilité 
la fonction test_public a pour visibilité pub 
la fonction test_self a pour visibilité pub(self) 
la fonction test_super a pour visibilité pub(super) 
la fonction test_crate a pour visibilité pub(crate) 
la fonction test_in_path a pour visibilité pub(in module::mod2::test)

Dans le cas de test_private, l’absence de visibilitĂ© implique que la mĂ©thode est privĂ©e. Et donc notre macro n’a rien a afficher.

Attributs

Les attributes sont reprĂ©sentĂ©es par l’identifieur $x:meta.

Les attributs permettent de spécifier des comportements avancés sur des items.

Par exemple:

#[derive(Debug)]
struct A;

L’attribut #[derive(Debug)] rajoute le trait Debug à notre structure A.

Il existe des attributs internes ou externes

Les externes sont par exemple les #[derive(...)].

Et les internes.

fn with_unused_variables() {
    #![allow(unused_variables)]
    let x = 45;
}

Ici l’attribut se rapporte Ă  l’intĂ©rieur de la fonction, permettant de pas utiliser la variable x sans que le compilateur ne dĂ©clenche de warning.

Si on avait utilisé la version externe, ce serait la non-utilisation de la fonction with_unused_variables qui serait permise, mais la varible x déclencherait toujours des warnings.

Un exemple de macro pourrait ĂȘtre:

macro_rules! attribute_match {
    (#![$attr:meta]) => {
        println!("#[{}] is an outer attribute", stringify!($attr))
    };
    (#[$attr:meta]) => {
        println!("!#[{}] is an inner attribute", stringify!($attr))
    };
}

fn main() {
    attribute_match! {
        #[derive(Debug)]
    }

    attribute_match! {
        #![derive]
    }
}

Affiche:

!#[derive(Debug)] is an inner attribute
#[derive] is an outer attribute

Et voilĂ  fini on peut tout matcher dans le code !! đŸ€©

Parlons de concepts un plus évolué maintenant.

HygiĂšne

Contrairement aux macros en langage C qui sont littéralement du remplacement de chaßne de caractÚres.

Les macros sont conscientes des variables qu’elles manipulent.

Ainsi pour des raisons de sĂ©curitĂ©, les variables dĂ©clarĂ©es Ă  l’extĂ©rieur des macros ne peuvent pas ĂȘtre manipulĂ©es par celles-ci. Sauf si elles sont dans le mĂȘme scope.

Prenons un exemple:

let mut x = 41;

macro_rules! inc {
    () => {
        x+=1
    };
}
inc!();
println!("{}", x)

Ce code affiche 42, car le x est dĂ©clarĂ© dans le mĂȘme scope que la macro inc!.

Par contre:

macro_rules! inc {
    () => {
        x+=1
    };
}

let mut x = 41;

inc!();
println!("{}", x)

Ce code ne compile pas, car x n’est pas connu au moment de l’expansion de la macro inc!.

Pour “rĂ©parer” cette macro nous devons lui indiquer la rĂ©fĂ©rence vers la variable x.

macro_rules! inc {
    ($x:ident) => {
        $x+=1
    };
}

let mut x = 41;

inc!(x);
println!("{}", x)

Et maintenant cela remarche. 😃

Les macros peuvent venir de n’importe oĂč, et parfois on ne regarde pas ce qu’il y a dedans (toujours, en fait, ne mentez pas, je vous vois 👀).

Prenons un exemple:

let mut x = 41;

macro_rules! devil {
    () => {
        let mut x = 666;
        x+=1;
    };
}
devil!();
println!("{}", x)

Imaginons que l’on appelle notre macro devil!(). En l’absence du systùme d’hygiùne, le code devrait afficher 667.

Mais grĂące Ă  ce systĂšme on vient en quelque sorte encapsuler ce let x dans un scope interne. L’incrĂ©mentation elle mĂȘme change de comportement et au lieu de venir incrĂ©menter le x de l’extĂ©rieur de la macro, on incrĂ©ment le x interne.

Ce qui nous permet de préserver la valeur afficher à 41.

Cette macro peut alors ĂȘtre dĂ©placĂ©e au dessus de la dĂ©claration de x.

macro_rules! devil {
    () => {
        let mut x = 666;
        x+=1;
    };
}


let mut x = 41;
devil!();
println!("{}", x)

La macro devil!() peut maintenant ĂȘtre appelĂ©e avant la dĂ©claration de x car elle dĂ©finit son propre x qui n’a rien Ă  voir (n’est pas la mĂȘme case mĂ©moire) par rapport Ă  celui affichĂ© dans le println!.

Expansion en cascade

L’expansion imbriquĂ©e est la capacitĂ© d’une macro d’en contenir une autre.

Exemple:

macro_rules! a {
    () => {
        1
    };
}

macro_rules! b {
    ($x:expr) => {
        $x + 1
    };
}

macro_rules! c {
    ($x:expr) => {
        println!("{}", $x)
    };
}

fn main() {
    c!(b!(a!()))
}

Affiche

2

Que se passe-t-il ?

Si on décompose, nous allons avoir:

  1. initialement nous avons c!(b!(a!()))
  2. qui s’étend en println!("{}", b!(a!()))
  3. qui s’étend en println!("{}", a!() + 1)
  4. qui s’étend en println!("{}", 1 + 1)

On voit donc que l’expansion des macros se fait de l’extĂ©rieur vers l’intĂ©rieur.

Il faut donc faire attention Ă  l’ordre d’appel des macros pour obtenir le rĂ©sultat souhaitĂ© !

Une autre remarque est Ă  toute Ă©tape de l’expansion, le code doit rester valide.

Une macro peut bien s’appeler elle-mĂȘme tant qu’une de ses variantes match et nous allons nous en servir dans la partie suivante. 🙂

c!(b!(b!(a!())))

est tout aussi valide et affichera 3.

Je vous laisse faire l’expansion pour vous en convaincre ^^

On met en pratique

Je sais pas si vous vous en souvenez mais Ă  la base on voulait rĂ©aliser la somme d’un nombre arbitraire d’opĂ©randes.

C’est ce que l’on va faire tout de suite ! Je vous indique une solution et on la dĂ©cortique! 😀

macro_rules! sum {
    () => {
        0
    };
    ($x:expr) => {
        $x
    };
    ($x0:expr, $($x:expr),*) => {
       $x0 + sum!($($x),*)
    };
}

fn main() {
    println!("{}", sum!(6, 7, 4, 5));
}

Si vous éxécutez ce code vous afficherez 22.

Que se passe-t-il ?

  1. initialement nous avons sum!(6, 7, 4, 5)
  2. On match les patterns:
    1. ce n’est pas une parenthùse vide donc pas ()
    2. ce n’est pas non plus une expressions donc pas ($x:expr)
    3. ça commence par une expression suivi d’une virgule et optionnellement quelque chose aprùs donc le pattern ($x0:expr, $($x:expr),*) match !
  3. on remplace l’expression par 6 + sum!(7, 4, 5)
  4. MĂȘme systĂšme le seul pattern qui match est ($x0:expr, $($x:expr),*)
  5. on remplace l’expression par 6 + (7 + sum!(4, 5))
  6. le seul pattern qui match est ($x0:expr, $($x:expr),*)
  7. on remplace l’expression par 6 + (7 + (4 + sum!(5))
  8. cette fois ci le seul pattern qui match est ($x:expr)
  9. on remplace par 6 + (7 + (4 + 5))

En nettoyant les parenthùses qui n’ont pas d’incidence, on retrouve bien 6 + 7 + 4 + 5 = 22. 🎉

Ouf !! On a rĂ©ussi! 😄

Maintenant on a tous les outils pour bosser efficacement avec les macros !

Conclusion

J’espùre que ce petit tour des macro_rules en Rust vous plu! 😎

Nous allons dans le prochaine Ă  paraĂźtre pour construire un systĂšme trĂšs automatisĂ© qui nous Ă©pargenera l’écriture de beaucoup de lignes de codes. đŸ’Ș

Je vous remercie de votre lecture et vous dis à la prochaine pour plus de Rust ! 😍