宏看起来和函数很像,只不过名称末尾有一个感叹号 ! 。
宏并不产生函数调用,而是展开成源码,并和程序的其余部分一起被编译。
Rust宏和C不同,Rust的宏会展开为抽象语法树(AST,abstract syntax tree),而不是直接字符串替换,这样就不会产生无法预料的优先级错误。
宏有两种:声明宏和过程宏
(一)定义
声明宏通过macro_rules! 创建
每个声明宏都有一个名称和一条或多条规则。
每条规则都有两部分:一个匹配器(matcher),描述它匹配的句法;一个转换器(transcriber),描述成功匹配后将替代调用的句法。
匹配器和转换器都必须由定界符(delimiter)包围。
宏可以替换为表达式、语句、程序项、类型、模式
1.语法格式
macro_rules! macro_name {
// 匹配器和转换器
(pattern1) => { /* code1 */ };
(pattern2) => { /* code2 */ };
...
}
其中macro_name是宏的名字,pattern是匹配器,要匹配的模式,code是转换器,需要替换成的代码。可以看出跟match非常相似。
2.宏参数
在匹配器中使用$ IDENTIFIER : MacroFragSpec捕获宏参数
在转换器中使用$ IDENTIFIER表示宏参数
MacroFragSpec叫做指示符,表示参数的类型
有下列这些指示符
item条目, 例如函数、结构体、模块等
block代码块,
stmt语句(statement)
pat模式(pattern)
expr表达式
ty类型(type) 例如u8 u16
ident标识符 例如结构体、函数的名字
path路径 例如::std::mem::replace
meta元条目 例如 #[…] #![…]
tt标记树 token tree
vis可见性描述符 例如pub
literal 用于字面量
meta 元信息,例如 #[…]和 #![rust macro…] 属性
例子
macro_rules! create_function {
// 此宏接受一个 `ident` 类型的参数,并创建一个名为 `$func_name` 的函数。
($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
});
}
3.宏重载
宏可以重载,从而接受不同的参数组合。也就是说,宏可以有多条规则。
类似于match代码块
例子
// 根据你调用它的方式,`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);
}
4.参数重复
在匹配器中使用 + 来表示可能出现一个或多个参数,使用 * 来表示出现零个或多个参数,用于可变参数列表。
* — 表示任意数量的重复元。
+ — 表示至少有一个重复元。
? — 表示一个可选的匹配段,可以出现零次或一次
例子
// `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));
}
5.作用域
#[macro_export] 表示只要导入了定义这个宏的 crate,该宏就应该是可用的。如果没有该属性,这个宏就不能被引入
例子
#[macro_export]
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
(二)使用
宏调用是在编译时执行宏,用执行结果替换该调用。
通过宏名后跟一个!和一个参数列表调用宏
比如
some_extension!(...)
some_extension!{...}
some_extension![...]
例子
// 作为表达式使用
let x = vec![1,2,3];
// 作为语句使用
println!("Hello!");
// 在模式中使用
macro_rules! pat {
($i:ident) => (Some($i))
}
if let pat!(x) = Some(1) {
assert_eq!(x, 1);
}
// 在类型中使用
macro_rules! Tuple {
{ $A:ty, $B:ty } => { ($A, $B) };
}
type N2 = Tuple!(i32, i32);
// 作为程序项使用
thread_local!(static FOO: RefCell = RefCell::new(1));
// 作为关联程序项使用
macro_rules! const_maker {
($t:ty, $v:tt) => { const CONST: $t = $v; };
}
trait T {
const_maker!{i32, 7}
}
// 宏内调用宏
macro_rules! example {
() => { println!("Macro call in a macro!") };
}
// 外部宏 `example` 展开后, 内部宏 `println` 才会展开.
example!();
过程宏(procedural macros),更像函数。过程宏接收一些 代码,然后产生另一些代码,而非像声明式宏那样匹配对应模式然后以另一部分代码替换当前代码。
过程宏在编译时运行
过程宏必须在 crate类型为 proc-macro 的crate中定义。这种类型的crate总是链接编译器提供的 proc_macro crate。proc_macro crate提供了编写过程宏所需的各种类型和工具来让编写更容易。此crate主要包含了一个 TokenStream 类型。
注意: 使用Cargo时,定义过程宏的crate的配置文件里要做如下设置:
[lib]
proc-macro = true
过程宏有三种:
(一)类函数过程宏
类函数过程宏是使用宏调用运算符(!)调用的过程宏。
这种宏是由一个带有 proc_macro属性和 (TokenStream) -> TokenStream签名的 公有可见性函数定义。
输入 TokenStream 是由宏调用的定界符界定的内容,输出 TokenStream 将替换整个宏调用。
例如,下面的宏定义忽略它的输入,并将函数 answer 输出到它的作用域
#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro]
pub fn make_answer(_item: TokenStream) -> TokenStream {
"fn answer() -> u32 { 42 }".parse().unwrap()
}
然后我们用它在一个二进制crate里打印 “42” 到标准输出。
extern crate proc_macro_examples;
use proc_macro_examples::make_answer;
make_answer!();
fn main() {
println!("{}", answer());
}
(二)派生宏
派生宏为派生(derive)属性定义新输入。这类宏在给定输入结构体(struct)、枚举(enum)或联合体(union) token流的情况下创建新程序项。它们也可以定义派生宏辅助属性。
派生宏由带有 proc_macro_derive属性和 (TokenStream) -> TokenStream签名的公有可见性函数定义。
输入TokenStream 是带有 derive 属性的程序项的token流。输出TokenStream必须是一组程序项,然后将这组程序项追加到输入TokenStream中的那条程序项所在的模块或块中。
下面是派生宏的一个示例。它没有对输入执行任何有用的操作,只是追加了一个函数 answer。
#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_derive(AnswerFn)]
pub fn derive_answer_fn(_item: TokenStream) -> TokenStream {
"fn answer() -> u32 { 42 }".parse().unwrap()
}
然后使用这个派生宏:
extern crate proc_macro_examples;
use proc_macro_examples::AnswerFn;
#[derive(AnswerFn)]
struct Struct;
fn main() {
assert_eq!(42, answer());
}
派生宏辅助属性
派生宏可以将额外的属性添加到它们所在的程序项的作用域中。这些属性被称为派生宏辅助属性。这些属性是惰性的,它们存在的唯一目的是将这些属性在使用现场获得的属性值反向输入到定义它们的派生宏中。也就是说所有该宏的宏应用都可以看到它们。
定义辅助属性的方法是在 proc_macro_derive 宏中放置一个 attributes 键,此键带有一个使用逗号分隔的标识符列表,这些标识符是辅助属性的名称。
例如,下面的派生宏定义了一个辅助属性 helper,但最终没有用它做任何事情。
#![crate_type="proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_derive(HelperAttr, attributes(helper))]
pub fn derive_helper_attr(_item: TokenStream) -> TokenStream {
TokenStream::new()
}
然后在一个结构体上使用这个派生宏:
#[derive(HelperAttr)]
struct Struct {
#[helper]
field: ()
}
(三)属性宏
属性宏用于自定义属性。虽然rust有许多内置属性,但有时我们需要自定义属性,这就是宏属性。
属性宏由带有 proc_macro_attribute属性和 (TokenStream, TokenStream) -> TokenStream签名的公有可见性函数定义。
签名中的第一个 TokenStream 是属性名称后面的定界token树(delimited token tree)(不包括外层定界符)。如果该属性作为裸属性(bare attribute)给出,则第一个 TokenStream 值为空。第二个 TokenStream 是程序项的其余部分,包括该程序项的其他属性。输出的 TokenStream 将此属性宏应用的程序项替换为任意数量的程序项。
例如,下面这个属性宏接受输入流并按原样返回,实际上对属性并无操作。
#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_attribute]
pub fn return_as_is(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
下面示例在编译时输出
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_attribute]
pub fn show_streams(attr: TokenStream, item: TokenStream) -> TokenStream {
println!("attr: \"{}\"", attr.to_string());
println!("item: \"{}\"", item.to_string());
item
}
使用属性宏
extern crate my_macro;
use my_macro::show_streams;
// 示例: 基础函数
#[show_streams]
fn invoke1() {}
// out: attr: ""
// out: item: "fn invoke1() { }"
// 示例: 带输入参数的属性
#[show_streams(bar)]
fn invoke2() {}
// out: attr: "bar"
// out: item: "fn invoke2() {}"
// 示例: 输入参数中有多个token的
#[show_streams(multiple => tokens)]
fn invoke3() {}
// out: attr: "multiple => tokens"
// out: item: "fn invoke3() {}"
// 示例:
#[show_streams { delimiters }]
fn invoke4() {}
// out: attr: "delimiters"
// out: item: "fn invoke4() {}"