Zach的博客

Procedural Macro

Procedural Macros

Rust提供了一个derive的机制可以很方便地生成一些代码,在Rust 1.15以前定制derive的功能只在nightly里才有,Rust 1.15把这一个功能稳定了,也就是说我们可以在stable的版本里面定制derive了。

Rust book里面有一个章节Procedural Macros简单介绍了如何编写一个derive,最后的效果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#[macro_use]
extern crate hello_world_derive;

trait HelloWorld {
fn hello_world(&self);
}

#[derive(HelloWorld)]
struct Test1;

#[derive(HelloWorld)]
struct Test2;

fn main() {
// println!("Hello, world!");

let t1 = Test1;
let t2 = Test2;

t1.hello_world(); // print "Hello World from Test1"
t2.hello_world(); // print "Hello World from Test2"
}

hello_world_derive

接下来就解析一下具体的细节。

我们需要新建一个crate,这个crate里面包含自动生成代码的细节,我们将其命名为hello_world_derive。在现阶段,必须把这些实现放在另外一个crate中,不过在将来可能就不需要这么麻烦了。

新建的crate的Cargo.toml文件如下:

1
2
3
4
5
6
7
8
9
10
11
[package]
name = "hello_world_derive"
version = "0.1.0"
authors = ["Zach <zach_41@163.com>"]

[dependencies]

quote = "*"
syn = "*"

[lib]

proc-macro = true

proc-macro 值为true表示这是一个Procedural Macro的crate。

lib.rs里面的代码如下:

1
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
extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;

use proc_macro::TokenStream;

fn impl_hello_world(ast: &syn::MacroInput) -> quote::Tokens {
let name = &ast.ident;

quote! {
impl HelloWorld for #name {
fn hello_world(&self) {
println!("Hello World from {}!", stringify!(#name));
}
}
}
}

#[proc_macro_derive(HelloWorld)]
pub fn hello_world(input: TokenStream) -> TokenStream {
let input = input.to_string();

let ast = syn::parse_macro_input(&input).unwrap();

let gen = impl_hello_world(&ast);

gen.parse().unwrap()
}

先看hello_world函数,其输入是一个TokenStream,现阶段我们只能调用to_string,将它转换成字符串,然后我们调用syn模块里面的parse函数,得到一个抽象的语法树AST(类型为MacroInput)。

implies_hello_world中我们调用分析得到的AST,并利用quote模块里的quote宏来生成需要的代码,这样就完成了自动生成了。

这里还有一个更复杂的例子derive_new,学习它对熟悉derive机制非常有帮助。