15. macro_rules!▲
Rust fournit un puissant système de macros qui permet la métaprogrammation. Comme vous l'avez vu dans les chapitres précédents, les macros ressemblent aux fonctions, excepté que leurs identificateurs se terminent par un point d'exclamation !
, mais au lieu de générer un appel de fonction, les macros sont étendues dans le code source et compilées avec le reste du programme.
Il est possible de créer une macro en utilisant la macro macro_rules!
.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
// Une simple macro nommée `say_hello`.
macro_rules!
say_hello {
// `()` indique que la macro ne prend aucun argument.
() => (
// La macro étendra le contenu de ce bloc.
println!
("Hello!"
);
)
}
fn
main() {
// Cet appel va étendre `println!("Hello");`.
say_hello!
()
}
15-1. Les indicateurs▲
Les arguments d'une macro sont préfixés par un $
et sont typés par le biais d'un indicateur :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
macro_rules!
create_function {
// Cette macro prend un argument possédant l'indicateur `ident`
// et créé une fonction nommée `$func_name`.
// L'indicateur `ident` est utilisé pour les identificateur de variables/fonctions.
($func_name
:ident) => (
fn
$func_name
() {
// La macro `stringify!` convertit un `ident` en chaîne de caractères.
println!
("You called
{:?}
()"
,
stringify!
($func_name
))
}
)
}
// On créé les fonctions `foo` et `bar` avec la macro ci-dessus.
create_function!
(foo);
create_function!
(bar);
macro_rules!
print_result {
// Cette macro prend une expression de type `expr` et
// affiche sa représentation sous forme de chaîne de caractères
// ainsi que son résultat.
($expression
:expr) => (
// `stringify!` va convertir l'expression *telle qu'elle est* dans une chaîne
// de caractères.
println!
("
{:?}
=
{:?}
"
,
stringify!
($expression
),
$expression
)
)
}
fn
main() {
foo();
bar();
print_result!
(1
u32 + 1
);
// Rappelez-vous que les blocs sont également des expressions !
print_result!
({
let
x = 1
u32;
x * x + 2
* x - 1
});
}
Voici une liste exhaustive des indicateurs existants :
- block ;
- expr, pour les expressions ;
- ident, pour les identificateurs de variables et fonctions ;
- item ;
- pat (pattern) ;
- path ;
- stmt (déclaration) ;
- token (token tree) ;
- ty (type).
15-2. Surcharge▲
Les macros peuvent être surchargées pour accepter différentes combinaisons d'arguments. Dans cet esprit, macro_rules!
peut fonctionner de la même manière qu'un bloc match
:
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
// `test!` va comparer `$left` et `$right` de différentes
// manières suivant son utilisation à l'invocation:
macro_rules!
test {
// Il n'est pas nécessaire de séparer les arguments par des virgules.
// N'importe quel modèle peut être utilisé !
($left
:expr; and $right
:expr) => (
println!
("
{:?}
and
{:?}
is
{:?}
"
,
stringify!
($left
),
stringify!
($right
),
$left
&&
$right
)
);
// ^ Chaque branche doit se terminer par un point-virgule.
($left
:expr; or $right
:expr) => (
println!
("
{:?}
or
{:?}
is
{:?}
"
,
stringify!
($left
),
stringify!
($right
),
$left
|| $right
)
);
}
fn
main() {
test!
(1
i32 + 1
== 2
i32; and 2
i32 * 2
== 4
i32);
test!
(true
; or false
);
}
15-3. Répétition▲
Les macros peuvent utiliser le quantificateur + dans la liste des arguments pour indiquer qu'un argument peut être répété au moins une(1) fois ou * pour indiquer que l'argument peut être répété zéro(0) ou plusieurs fois.
Dans l'exemple suivant, entourer le matcher avec $
(...),+ va permettre la capture d'une ou plusieurs expressions, séparées par des virgules. Notez également que le point-virgule est optionnel dans le dernier cas (i.e. la dernière expression capturée).
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
// `min!` va calculer le minimum entre chaque argument passé, peu importe le nombre.
macro_rules!
find_min {
// Expression de base:
($x
:expr) => ($x
);
// `$x` suivi par au moins un `$y,`.
($x
:expr, $
($y
:expr),+) => (
// On appelle `find_min` sur les $y suivants`.
std::cmp::min($x
, find_min!
($
($y
),+))
)
}
fn
main() {
println!
("
{}
"
, find_min!
(1
u32));
println!
("
{}
"
, find_min!
(1
u32 + 2
, 2
u32));
println!
("
{}
"
, find_min!
(5
u32, 2
u32 * 3
, 4
u32));
}
15-4. DRY (Don't Repeat Yourself)▲
Les macros permettent l'écriture de code DRY en recyclant les parties communes d'un ensemble de fonctions/tests. Voici un exemple qui implémente et test les opérateurs +=, *= et -= sur la structure Vec<T> :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
// Dans le fichier dry.rs
use
std::ops::{Add, Mul, Sub};
macro_rules!
assert_equal_len {
// L'indicateur `tt` (token tree) est utilisé pour les opérateurs
// et les tokens.
($a
:ident, $b
: ident, $func
:ident, $op
:tt) => (
assert!
($a
.len() == $b
.len(),
"
{:?}
: dimension mismatch:
{:?}
{:?}
{:?}
"
,
stringify!
($func
),
($a
.len(),),
stringify!
($op
),
($b
.len(),));
)
}
macro_rules!
op {
($func
:ident, $bound
:ident, $op
:tt, $method
:ident) => (
fn
$func
<T: $bound
<T, Output=T> + Copy>(xs: &
mut
Vec<T>, ys: &
Vec<T>) {
assert_equal_len!
(xs, ys, $func
, $op
);
for
(x, y) in
xs.iter_mut().zip(ys.iter()) {
*x = $bound
::$method
(*x, *y);
// *x = x.$method(*y);
}
}
)
}
// On implémente les fonctions `add_assign`, `mul_assign` et `sub_assign`.
op!
(add_assign, Add, +=, add);
op!
(mul_assign, Mul, *=, mul);
op!
(sub_assign, Sub, -=, sub);
mod
test {
use
std::iter;
macro_rules!
test {
($func
: ident, $x
:expr, $y
:expr, $z
:expr) => {
#[test]
fn
$func
() {
for
size in
0
usize..10
{
let
mut
x: Vec<_> = iter::repeat($x
).take(size).collect();
let
y: Vec<_> = iter::repeat($y
).take(size).collect();
let
z: Vec<_> = iter::repeat($z
).take(size).collect();
super::$func
(&
mut
x, &
y);
assert_eq!
(x, z);
}
}
}
}
// Test de `add_assign`, `mul_assign` et `sub_assign`.
test!
(add_assign, 1
u32, 2
u32, 3
u32);
test!
(mul_assign, 2
u32, 3
u32, 6
u32);
test!
(sub_assign, 3
u32, 2
u32, 1
u32);
}
2.
3.
4.
5.
6.
7.
$
rustc --test dry.rs &&
./dry
running 3
tests
test test::mul_assign ... ok
test test::add_assign ... ok
test test::sub_assign ... ok
test result: ok. 3
passed; 0
failed; 0
ignored; 0
measured