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:
- les macro_rules
- proc_macro
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 callPour 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:
$(...)*: zéro ou plusieurs fois$(...)+: une ou plusieurs fois$(...)?: une ou zéro fois
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:
vec![: on écrit les caractÚres tels quels- on rentre dans la répétition
- le
$xmatch lâexpression1: on Ă©critvec![1 + 2 - un sĂ©parateur
,est rajoutévec![1 + 2, - le
$xmatch lâexpression3: on Ă©critvec![1 + 2, 3 + 2 - un sĂ©parateur
,est rajoutévec![1 + 2, 3 + 2, - le
$xmatch lâexpression5: on Ă©critvec![1 + 2, 3 + 2, 5 + 2 - il nây plus dâexpression Ă matcher, on sort de la rĂ©pĂ©tition
- 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 = 2Ici 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 unidenten string et ainsi pouvoir lâafficher dans unprintln!
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
AEt 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 AChemin
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::tutuToken Tree
Les token tree sont reprĂ©sentĂ©s par lâidentifieur $x:tt.
Ils permettent de matcher tout ce qui peut se trouver entre:
(et)[et]{et}
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 identLifetime
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 'bVisibilité
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.
- pub
- pub (crate)
- pub (self)
- pub (super)
- pub (in path)
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 attributeEt 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
2Que se passe-t-il ?
Si on décompose, nous allons avoir:
- initialement nous avons
c!(b!(a!())) - qui sâĂ©tend en
println!("{}", b!(a!())) - qui sâĂ©tend en
println!("{}", a!() + 1) - 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 ?
- initialement nous avons
sum!(6, 7, 4, 5) - On match les patterns:
- ce nâest pas une parenthĂšse vide donc pas
() - ce nâest pas non plus une expressions donc pas
($x:expr) - ça commence par une expression suivi dâune virgule et optionnellement quelque chose aprĂšs donc le pattern
($x0:expr, $($x:expr),*)match !
- ce nâest pas une parenthĂšse vide donc pas
- on remplace lâexpression par
6 + sum!(7, 4, 5) - MĂȘme systĂšme le seul pattern qui match est
($x0:expr, $($x:expr),*) - on remplace lâexpression par
6 + (7 + sum!(4, 5)) - le seul pattern qui match est
($x0:expr, $($x:expr),*) - on remplace lâexpression par
6 + (7 + (4 + sum!(5)) - cette fois ci le seul pattern qui match est
($x:expr) - 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 ! đ