Rust Macros
Table of Contents
1. 宏简介
宏可以生成代码,也就是说在调用宏的地方,编译器会先将宏进行展开,然后再编译展开后的代码。 很多语言都支持宏,如 C/Lisp/Rust 等等,本文介绍 Rust 中的宏。
Rust 中使用 macro_rules!
(它本身也是宏)可以定义一个宏。下面例子中,使用 macro_rules!
定义了一个名为 say_hello
的宏:
// This is a simple macro named `say_hello`. macro_rules! say_hello { // `()` indicates that the macro takes no argument. () => { // The macro will expand into the contents of this block. println!("Hello!"); }; } fn main() { // This call will expand into `println!("Hello!");` say_hello!() }
Rust 中宏看起来像函数,调用宏时在宏名后面加个 !
即可,如常见的 println!
就是宏调用, 上面例子中出现的 say_hello!
和 macro_rules!
都是宏调用。
1.1. 查看宏展开后的代码
使用 rustc -Z unstable-options --pretty=expanded
可以查看宏展开后的代码。下面是个例子:
$ cat test.rs fn main() { println!("Hello world"); // println 是宏 } $ rustc -Z unstable-options --pretty=expanded test.rs #![feature(prelude_import)] #![no_std] #[prelude_import] use ::std::prelude::v1::*; #[macro_use] extern crate std; fn main() { { ::std::io::_print(::core::fmt::Arguments::new_v1(&["Hello world\n"], &match () { () => [], })); }; }
1.2. 宏和函数的区别
宏和函数的区别:
- 宏是一种生成其他代码的方式,即所谓的元编程(Metaprogramming)。
- Rust 中函数不支持变长参数。相比之下,宏能够接受不同数量的参数。
- 宏比函数复杂,通过宏定义要比函数定义更难阅读、理解以及维护。
- 宏和函数的最后一个重要的区别是:在一个文件里调用宏之前必须定义它,或将其引入作用域,而函数则可以在任何地方定义和调用。
2. 宏语法
下面我们看个简单例子来说明一下 Rust 中的宏展开:
macro_rules! foo { ($x:ident) => { $x * $x } // $x 是宏参数;indent 是宏参数的类型指示符,这里表示 $x 必须是“标识符” // ^ ^ // | | // 宏参数 展开后的目标代码 } fn main() { let a = 5; let b = foo!(a); // foo!(a) 会被展开为 a * a println!("b = {}", b) }
宏中的参数都是以 $
开头, 可以对宏变量类型使用指示符(designator)加以限定,就跟普通的变量绑定一样用“冒号”将变量和类型分开,宏支持以下几种指示符:
- item: an Item
- block: a BlockExpression
- stmt: a Statement without the trailing semicolon (except for item statements that require semicolons)
- pat: a Pattern
- expr: an Expression
- ty: a Type
- ident: an IDENTIFIER_OR_KEYWORD
- path: a TypePath style path
- tt: a TokenTree (a single token or tokens in matching delimiters (), [], or {})
- meta: an Attr, the contents of an attribute
- lifetime: a LIFETIME_TOKEN
- vis: a possibly empty Visibility qualifier
- literal: matches LiteralExpression
相对于 C 中纯文本替换的宏,Rust 中宏可以对变量类型加以限定,这更加安全可靠。比如,前面例子中,我们不能把 $x
的类型指示符修改为 ty
(表示“类型”),因为两个 ty
相乘没有意义。
macro_rules! foo { ($x:ty) => { $x * $x } // 这是错误的,两个 ty 相乘没有意义 }
此外,宏参数也可以使用 {}
包围,展开体也可以使用 ()
包围,即前面的 foo
也可以写为:
macro_rules! foo { {$x:ident} => ( $x * $x ) // 参数和展开体可以使用 {} 或 () 包围,是一对就行 }
2.1. 宏展开实例:创建函数
下面看一个宏的例子,这个宏会展开为一个函数:
macro_rules! create_function { ($func_name:ident) => { fn $func_name() { println!("function {:?} is called", stringify!($func_name)) } } } fn main() { create_function!(foo); // 展开为一个名为 foo 的函数 foo(); create_function!(bar); // 展开为一个名为 bar 的函数 bar(); }
2.2. 宏展开实例:多个参数 Pattern
下面看一个宏的例子,参数可以是不同的 Pattern:
macro_rules! add { (one to $input:expr) => {$input + 1}; (two to $input:expr) => {$input + 2}; } fn main() { println!("Add one: {}", add!(one to 10)); // add!(one to 10) 会展开为 10 + 1 println!("Add two: {}", add!(two to 10)); // add!(two to 10) 会展开为 10 + 2 }
2.3. 宏重复(可实现变长参数)
Rust 宏中支持“重复”,被重复的项需要放在 $(...)
中,后面接 *
(表示任意重复)或者 +
(表示至少一个)或者 ?
(表示零个或者一个)。在 */+/?
之前可以有个分隔符,分隔符常常是 ,
或者 ;
。注:重复的最后一个项目没有分隔符。
比如, $( $i:ident ),*
表示任意个使用逗号分隔的标识符。
macro_rules! vector { // 版本一 ($($x:expr),*) => { // ^ ^ // | | // 表示重复 重复标记 * 前面的这个逗号为分隔符 { let mut temp_vec = Vec::new(); $(temp_vec.push($x);)* // ^\ / ^ // | -----重复项----- | // 表示重复 重复标记 * 前面没有分隔符(分号在重复项中,并不是分隔符) temp_vec } }; } fn main() { let a = vector![1, 2, 3]; println!("{:?}", a); }
上面代码中 vector![1, 2, 3]
会被展开为:
{ let mut temp_vec = Vec::new(); temp_vec.push(1); temp_vec.push(2); temp_vec.push(3); temp_vec }
宏 vector 的定义中, $(temp_vec.push($x);)*
可以改写为 $(temp_vec.push($x));*
吗?答案是可以的,只是麻烦一些。由于重复的最后一个项目没有分隔符,所以 $(temp_vec.push($x));*
在展开时会生成:
temp_vec.push(1); temp_vec.push(2); temp_vec.push(3) // 最后一项没分隔符,这里还少个分号
这样,我们还需要额外增加一个分号,如:
macro_rules! vector { // 版本二 ($($x:expr),*) => { { let mut temp_vec = Vec::new(); $(temp_vec.push($x));* // ^\ / ^ // | -----重复项---- | // 表示重复 重复标记前面有个分号分隔符 ; // 重复的最后一个项目没有分隔符,这里需要额外增加一个分号 temp_vec } }; }
2.3.1. 宏重复的实例
下面是个宏重复的实例:
// from https://doc.rust-lang.org/rust-by-example/macros/repeat.html // // `find_min!` will calculate the minimum of any number of arguments. macro_rules! find_min { // Base case: ($x:expr) => ($x); // `$x` followed by at least one `$y,` ($x:expr, $($y:expr),+) => ( // $($y:expr),+ 是宏重复 // Call `find_min!` on the tail `$y` std::cmp::min($x, find_min!($($y),+)) // 递归调用宏 ) } fn main() { println!("{}", find_min!(1)); println!("{}", find_min!(1 + 2, 2)); println!("{}", find_min!(5, 2 * 3, 4)); }
2.4. macro_use, macro_export 属性
macro_use
属性有两个作用,下面将一一介绍。
macro_use
作用一:如果宏定义在 module 中,默认宏仅能在 module 内部使用,如果 module 上声明 macros_use
属性,则 module 内定义的宏的作用域不会结束,可以被外部使用。比如:
#[macro_use] // 表示 mod1 内的宏 m 可以被外部使用 mod mod1 { macro_rules! m { () => {}; } } fn main() { m!(); // 如果没有 #[macro_use],则这里会报错 }
需要说明的是, 在宏 m 上标记 macro_export
属性,可以让 m 被外部使用。 如:
mod mod1 { #[macro_export] macro_rules! m { () => {}; } } fn main() { m!(); // 宏名上指定 #[macro_export],也可以让宏在外部使用 }
macro_use
作用二:引入外部 crate 中定义的宏。比如外部 crate bar 中定义了宏 baz,可以这样引入进来:
// Rust 2015 #[macro_use(baz)] // 引入 bar 中的宏 baz,如果省略小括号,则引入 bar 中所有宏 extern crate bar; fn main() { baz!(); }
不过,Rust 2018 简化了这样的写法,上面代码可写为:
// Rust 2018 use bar::baz; fn main() { baz!(); }
2.5. 宏作用域
出于历史的原因,宏有两种形式的作用域:Textual scope 和 Path-based scope。两者的区别在于:如果调用宏时,直接使用不带 Path 的名称,则先在 Textual scope(当前环境)中寻找宏,找不到再去引入的 Path 中寻找宏;如果调用宏时,指定了 Path,则仅仅在引入的 Path 中寻找宏,忽略 Textual scope 中的宏。下面是个例子:
use lazy_static::lazy_static; // Path-based import,引入 crate lazy_static 中的宏 lazy_static macro_rules! lazy_static { // Textual definition,定义一个宏 lazy_static(同名宏不是好习惯,仅演示) () => {}; } lazy_static!{} // 调用宏时没有 Path,先找本文件中定义的 lazy_static,找不到再找引入的 lazy_static self::lazy_static!{} // 调用宏时有 Path `self::`,忽略本文件中定义的 lazy_static,仅找引入的 lazy_static fn main() {}
2.6. $crate 让宏行为更明确
在定义宏时,可以使用 Metavariable $crate
来表示定义宏的 crate 的起始 path,这可以使宏的行为更明确。如:
pub mod inner { pub fn foo() { println!("inner::foo called."); } #[macro_export] macro_rules! call_foo1 { () => { foo() }; } #[macro_export] macro_rules! call_foo2 { () => { $crate::inner::foo() }; // $crate 表示定义宏的 crate 的起始 path } } fn foo() { println!("foo called."); } fn main() { call_foo1!(); // 输出 foo called. call_foo2!(); // 输出 inner::foo called. }
3. Procedural Macros
除了 macro_rules!
定义宏外,过程宏(Procedural Macros)也可以生成新代码,过程宏有下面 3 种:
- 派生宏(Derive macro) -
#[derive(CustomDerive)]
,如#[derive(Debug)]
等 - 属性宏(Attribute macro) -
#[CustomAttribute]
,如#[inline]
等 - 函数式宏(Function-like macro) -
custom!(...)
关于 Procedural Macros 可参考:https://doc.rust-lang.org/reference/procedural-macros.html