Rust 提供了一个强大的宏系统,可进行元编程(metaprogramming)。
Rust 的宏会展开为抽象语法树(AST,abstract syntax tree),而不是像字符串预处理那样直接替换成代码,这样就不会产生无法预料的优先权错误。

宏是通过 macro_rules! 宏来创建的。

  1. // 这是一个简单的宏,名为 `say_hello`。
  2. macro_rules! say_hello {
  3. // `()` 表示此宏不接受任何参数。
  4. () => (
  5. // 此宏将会展开成这个代码块里面的内容。
  6. println!("Hello!");
  7. )
  8. }
  9. fn main() {
  10. // 这个调用将会展开成 `println("Hello");`!
  11. say_hello!()
  12. }

宏的作用:

  1. 不写重复代码(DRY,Don’t repeat yourself.)
  2. 领域专用语言(DSL,domain-specific language)。
  3. 可变接口(variadic interface)。接受不定数目参数的接口。

    语法

    基本概念有三个:
  • 模式与指示符
  • 重载
  • 重复

    指示符

    宏的参数使用一个美元符号 $ 作为前缀,并使用一个 指示符(designator) 注明类型:
    全部指示符如下:

  • block

  • expr 用于表达式
  • ident 用于变量名和函数名
  • item
  • pat(模式 pattern)
  • path
  • stmt(语句 statement)
  • tt(标记树 token tree)
  • ty(type) ``rust macro_rules! create_function { // 此宏接受一个ident指示符表示的参数,并创建一个名为$func_name的函数。 //ident` 指示符用于变量名或函数名 ($func_name:ident) => (
    1. fn $func_name() {
    2. // `stringify!` 宏把 `ident` 转换成字符串。
    3. println!("You called {:?}()",
    4. stringify!($func_name))
    5. }
    ) }

// 借助上述宏来创建名为 foobar 的函数。 create_function!(foo); create_function!(bar);

macro_rules! print_result { // 此宏接受一个 expr 类型的表达式,并将它作为字符串,连同其结果一起 // 打印出来。 // expr 指示符表示表达式。 ($expression:expr) => ( // stringify! 把表达式原样转换成一个字符串。 println!(“{:?} = {:?}”, stringify!($expression), $expression) ) }

fn main() { foo(); bar();

  1. print_result!(1u32 + 1);
  2. // 回想一下,代码块也是表达式!
  3. print_result!({
  4. let x = 1u32;
  5. x * x + 2 * x - 1
  6. });

}

  1. <a name="1IGXe"></a>
  2. ### 重载
  3. 宏可以重载,从而接受不同的参数组合。
  4. ```rust
  5. // 根据你调用它的方式,`test!` 将以不同的方式来比较 `$left` 和 `$right`。
  6. macro_rules! test {
  7. // 参数不需要使用逗号隔开。
  8. // 参数可以任意组合!
  9. ($left:expr; and $right:expr) => (
  10. println!("{:?} and {:?} is {:?}",
  11. stringify!($left),
  12. stringify!($right),
  13. $left && $right)
  14. );
  15. // ^ 每个分支都必须以分号结束。
  16. ($left:expr; or $right:expr) => (
  17. println!("{:?} or {:?} is {:?}",
  18. stringify!($left),
  19. stringify!($right),
  20. $left || $right)
  21. );
  22. }
  23. fn main() {
  24. test!(1i32 + 1 == 2i32; and 2i32 * 2 == 4i32);
  25. test!(true; or false);
  26. }

“1i32 + 1 == 2i32” and “2i32 * 2 == 4i32” is true “true” or “false” is true

重复

宏在参数列表中使用 + 来表示一个参数出现一次或多次,使用 * 表示该参数出现零次或多次。
$(...),+ 包围起来,就可以匹配一个或多个用逗号隔开的表达式。
宏定义的最后一个分支可以不用分号作为结束。

  1. // `min!` 将求出任意数量的参数的最小值。
  2. macro_rules! find_min {
  3. // 基本情形:
  4. ($x:expr) => ($x);
  5. // `$x` 后面跟着至少一个 `$y,`
  6. ($x:expr, $($y:expr),+) => (
  7. // 对 `$x` 后面的 `$y` 们调用 `find_min!`
  8. std::cmp::min($x, find_min!($($y),+))
  9. )
  10. }
  11. fn main() {
  12. println!("{}", find_min!(1u32));
  13. println!("{}", find_min!(1u32 + 2 , 2u32));
  14. println!("{}", find_min!(5u32, 2u32 * 3, 4u32));

DRY(不写重复代码)

这里给出一个例子,对 Vec<T> 实现 并测试了关于 +=*=-= 等运算符。

  1. use std::ops::{Add, Mul, Sub};
  2. macro_rules! assert_equal_len {
  3. // `tt`(token tree,标记树)指示符表示运算符和标记。
  4. ($a:ident, $b: ident, $func:ident, $op:tt) => (
  5. assert!($a.len() == $b.len(),
  6. "{:?}: dimension mismatch: {:?} {:?} {:?}",
  7. stringify!($func),
  8. ($a.len(),),
  9. stringify!($op),
  10. ($b.len(),));
  11. )
  12. }
  13. macro_rules! op {
  14. ($func:ident, $bound:ident, $op:tt, $method:ident) => (
  15. fn $func<T: $bound<T, Output=T> + Copy>(xs: &mut Vec<T>, ys: &Vec<T>) {
  16. assert_equal_len!(xs, ys, $func, $op);
  17. for (x, y) in xs.iter_mut().zip(ys.iter()) {
  18. *x = $bound::$method(*x, *y);
  19. // *x = x.$method(*y);
  20. }
  21. }
  22. )
  23. }
  24. // 实现 `add_assign`、`mul_assign` 和 `sub_assign` 等函数。
  25. op!(add_assign, Add, +=, add);
  26. op!(mul_assign, Mul, *=, mul);
  27. op!(sub_assign, Sub, -=, sub);
  28. mod test {
  29. use std::iter;
  30. macro_rules! test {
  31. ($func: ident, $x:expr, $y:expr, $z:expr) => {
  32. #[test]
  33. fn $func() {
  34. for size in 0usize..10 {
  35. let mut x: Vec<_> = iter::repeat($x).take(size).collect();
  36. let y: Vec<_> = iter::repeat($y).take(size).collect();
  37. let z: Vec<_> = iter::repeat($z).take(size).collect();
  38. super::$func(&mut x, &y);
  39. assert_eq!(x, z);
  40. }
  41. }
  42. }
  43. }
  44. // 测试 `add_assign`、`mul_assign` 和 `sub_assign`
  45. test!(add_assign, 1u32, 2u32, 3u32);
  46. test!(mul_assign, 2u32, 3u32, 6u32);
  47. test!(sub_assign, 3u32, 2u32, 1u32);
  48. }

执行: rustc --test dry.rs && ./dry

DSL(领域专用语言)

DSL 是 Rust 的宏中集成的微型 “语言”。

  1. macro_rules! calculate {
  2. (eval $e:expr) => {{
  3. {
  4. let val: usize = $e; // 强制类型为整型
  5. println!("{} = {}", stringify!{$e}, val);
  6. }
  7. }};
  8. }
  9. fn main() {
  10. calculate! {
  11. eval 1 + 2 // 看到了吧,`eval` 可并不是 Rust 的关键字!
  12. }
  13. calculate! {
  14. eval (1 + 2) * (3 / 4)
  15. }
  16. }

1 + 2 = 3 (1 + 2) * (3 / 4) = 0

可变参数接口

可变参数接口可以接受任意数目的参数。

  1. macro_rules! calculate {
  2. // 单个 `eval` 的模式
  3. (eval $e:expr) => {{
  4. {
  5. let val: usize = $e; // Force types to be integers
  6. println!("{} = {}", stringify!{$e}, val);
  7. }
  8. }};
  9. // 递归地拆解多重的 `eval`
  10. (eval $e:expr, $(eval $es:expr),+) => {{
  11. calculate! { eval $e }
  12. calculate! { $(eval $es),+ }
  13. }};
  14. }
  15. fn main() {
  16. calculate! { // 妈妈快看,可变参数的 `calculate!`!
  17. eval 1 + 2,
  18. eval 3 + 4,
  19. eval (2 * 3) + 1
  20. }
  21. }

1 + 2 = 3 3 + 4 = 7 (2 * 3) + 1 = 7