Rust 提供了一个强大的宏系统,可进行元编程(metaprogramming)。
Rust 的宏会展开为抽象语法树(AST,abstract syntax tree),而不是像字符串预处理那样直接替换成代码,这样就不会产生无法预料的优先权错误。
宏是通过 macro_rules! 宏来创建的。
// 这是一个简单的宏,名为 `say_hello`。macro_rules! say_hello {// `()` 表示此宏不接受任何参数。() => (// 此宏将会展开成这个代码块里面的内容。println!("Hello!");)}fn main() {// 这个调用将会展开成 `println("Hello");`!say_hello!()}
宏的作用:
- 不写重复代码(DRY,Don’t repeat yourself.)
- 领域专用语言(DSL,domain-specific language)。
- 可变接口(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) => (
) }fn $func_name() {// `stringify!` 宏把 `ident` 转换成字符串。println!("You called {:?}()",stringify!($func_name))}
// 借助上述宏来创建名为 foo 和 bar 的函数。
create_function!(foo);
create_function!(bar);
macro_rules! print_result {
// 此宏接受一个 expr 类型的表达式,并将它作为字符串,连同其结果一起
// 打印出来。
// expr 指示符表示表达式。
($expression:expr) => (
// stringify! 把表达式原样转换成一个字符串。
println!(“{:?} = {:?}”,
stringify!($expression),
$expression)
)
}
fn main() { foo(); bar();
print_result!(1u32 + 1);// 回想一下,代码块也是表达式!print_result!({let x = 1u32;x * x + 2 * x - 1});
}
<a name="1IGXe"></a>### 重载宏可以重载,从而接受不同的参数组合。```rust// 根据你调用它的方式,`test!` 将以不同的方式来比较 `$left` 和 `$right`。macro_rules! test {// 参数不需要使用逗号隔开。// 参数可以任意组合!($left:expr; and $right:expr) => (println!("{:?} and {:?} is {:?}",stringify!($left),stringify!($right),$left && $right));// ^ 每个分支都必须以分号结束。($left:expr; or $right:expr) => (println!("{:?} or {:?} is {:?}",stringify!($left),stringify!($right),$left || $right));}fn main() {test!(1i32 + 1 == 2i32; and 2i32 * 2 == 4i32);test!(true; or false);}
“1i32 + 1 == 2i32” and “2i32 * 2 == 4i32” is true “true” or “false” is true
重复
宏在参数列表中使用 + 来表示一个参数出现一次或多次,使用 * 表示该参数出现零次或多次。
$(...),+ 包围起来,就可以匹配一个或多个用逗号隔开的表达式。
宏定义的最后一个分支可以不用分号作为结束。
// `min!` 将求出任意数量的参数的最小值。macro_rules! find_min {// 基本情形:($x:expr) => ($x);// `$x` 后面跟着至少一个 `$y,`($x:expr, $($y:expr),+) => (// 对 `$x` 后面的 `$y` 们调用 `find_min!`std::cmp::min($x, find_min!($($y),+)))}fn main() {println!("{}", find_min!(1u32));println!("{}", find_min!(1u32 + 2 , 2u32));println!("{}", find_min!(5u32, 2u32 * 3, 4u32));
DRY(不写重复代码)
这里给出一个例子,对 Vec<T> 实现 并测试了关于 +=、*= 和 -= 等运算符。
use std::ops::{Add, Mul, Sub};macro_rules! assert_equal_len {// `tt`(token tree,标记树)指示符表示运算符和标记。($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);}})}// 实现 `add_assign`、`mul_assign` 和 `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 0usize..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);}}}}// 测试 `add_assign`、`mul_assign` 和 `sub_assign`test!(add_assign, 1u32, 2u32, 3u32);test!(mul_assign, 2u32, 3u32, 6u32);test!(sub_assign, 3u32, 2u32, 1u32);}
执行: rustc --test dry.rs && ./dry
DSL(领域专用语言)
DSL 是 Rust 的宏中集成的微型 “语言”。
macro_rules! calculate {(eval $e:expr) => {{{let val: usize = $e; // 强制类型为整型println!("{} = {}", stringify!{$e}, val);}}};}fn main() {calculate! {eval 1 + 2 // 看到了吧,`eval` 可并不是 Rust 的关键字!}calculate! {eval (1 + 2) * (3 / 4)}}
1 + 2 = 3 (1 + 2) * (3 / 4) = 0
可变参数接口
可变参数接口可以接受任意数目的参数。
macro_rules! calculate {// 单个 `eval` 的模式(eval $e:expr) => {{{let val: usize = $e; // Force types to be integersprintln!("{} = {}", stringify!{$e}, val);}}};// 递归地拆解多重的 `eval`(eval $e:expr, $(eval $es:expr),+) => {{calculate! { eval $e }calculate! { $(eval $es),+ }}};}fn main() {calculate! { // 妈妈快看,可变参数的 `calculate!`!eval 1 + 2,eval 3 + 4,eval (2 * 3) + 1}}
1 + 2 = 3 3 + 4 = 7 (2 * 3) + 1 = 7
