Rust 中的宏

  |   0 评论   |   0 浏览

(macro) 是 Rust 中的一种高级特性,Rust 中的宏分为两种:

  • 声明性宏(declarative macros)
  • 程序宏(procedural macros)
    • Custom #[derive] macros
    • Attribute-like macros
    • Function-like macros

宏与函数的区别

  • 函数不能接收任意多个参数,而宏可以。
  • 函数不能操作语法单元,而宏可以。从根本上说,宏是用来生成代码的。
  • 函数在编译之前不需要特殊处理,声明性宏需要在编译之前进行展开,展开之后代码会“膨胀”。
  • 函数的定义简单,而宏的定义和实现比函数更复杂。

使用宏的好处

  • 减少重复代码。
  • 通过宏可以定义 DSL(Domain-specific languages)。

宏的定义

宏的定义有两种方式:

  • Macros by Example define new syntax in a higher-level, declarative way.
  • Procedural Macros can be used to implement custom derive.

声明性宏

The most widely used form of macros in Rust is declarative macros. These are also sometimes referred to as “macros by example,” “macro_rules! macros,” or just plain “macros.”

声明性宏包含宏的名称和若干规则(rules),每个规则由两部分组成:matcher 和 transcriber,类似于模式匹配中的 pattern => do_something

声明性宏的定义方式

1macro_rules! $name {
2    $rule0 ;
3    $rule1 ;
4    // …
5    $ruleN ;
6}

每个 rule 的格式:($pattern) => {$expansion},其中括号和大括号不是特定的。可以使用 [](){} 中的任意一种,在调用宏的时候也是。

示例

 1// 定义一个生成函数的宏
 2macro_rules! create_function {
 3    ($func_name:ident) => (
 4        fn $func_name() {
 5            println!("function {:?} is called", stringify!($func_name))
 6        }
 7    )
 8}
 9
10fn main() {
11    // 调用之前定义的宏
12    create_function!(foo); // 也可以这样:create_function![foo];
13    foo();
14}

在上面的示例中,create_function 宏是用来生成函数的,其中, $func_name 是参数名,宏中的参数名都以 $ 开头。ident 的含义是标识符或关键字(identifier or keyword),可以被认为是一种参数类型。不过这是语法层面的类型(fragment specifiers),用来区分不同类型的代码片段,而 i32char 等这些都是语义层面的类型。在调用宏的时候,需要在宏名后加叹号!

宏名字的解析与函数略微有些不同,宏的定义必须出现在宏调用之前,而 Rust 函数则可以定义在函数调用后面。

Rust 支持以下这些“参数类型”(fragment specifiers):

  • item: an item, like a function, struct, module, etc.
  • block: block expression
  • 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 type path
  • tt: a token tree (a single token or tokens in matching delimiters (), [], or {})
  • meta: the contents of an attribute
  • lifetime: a lifetime token
  • vis: a possibly empty visibility qualifier
  • literal: literal expression

重复运算符

宏可以接收任意多个参数,例如 println!vec!。为了实现这个特性,Rust 提供了重复运算符(repetition operator):

  • * —— 任意次
  • + —— 一次或者多次
  • —— 零次或者一次

这些运算符在 matcher 和 transcriber 都可以使用。通常的格式是:$ ( ... ) sep rep,其中需要重复的内容放在$( ... )括号内,sep 是一个可选的分隔符(比如:逗号或分号),rep 是重复运算符。

示例

 1#[macro_export]
 2macro_rules! vec {
 3    ( $( $x:expr ),* ) => {
 4        {
 5            let mut temp_vec = Vec::new();
 6            $(
 7                temp_vec.push($x);
 8            )*
 9            temp_vec
10        }
11    };
12}

假如这样 vec![1, 2, 3] 调用宏时,将会生成如下代码:

1{
2    let mut temp_vec = Vec::new();
3    temp_vec.push(1);
4    temp_vec.push(2);
5    temp_vec.push(3);
6    temp_vec
7}

由于 $x 被匹配了三次,因此 temp_vec.push($x); 也会重复三次。

程序宏

程序宏有三种形式:

程序宏是在编译时运行的,我们可以将程序宏视为将一个 AST 转换为另一个 AST 的函数。

Function-like macros

Function-like 宏的定义与函数的定义类似,并且要添加 #[proc_macro] 属性。

示例

创建 macro_demo 项目

1cargo new macro_demo

Cargo.toml 文件中添加:

1[lib]
2proc-macro = true

src 目录下新建 lib.rs 文件,并添加以下内容:

1extern crate proc_macro; // 这是 Rust 提供的用来处理程序宏的
2use proc_macro::TokenStream;
3
4#[proc_macro]
5pub fn make_answer(_item: TokenStream) -> TokenStream {
6    "fn answer() -> u32 { 42 }".parse().unwrap()
7}

main.rs 中调用上面定义的程序宏:

1extern crate macro_demo;
2use macro_demo::make_answer;
3
4make_answer!();
5
6fn main() {
7    println!("{}", answer());
8}

Derive macros

derive 宏的定义与函数的定义类似,但需要添加 proc_macro_derive 属性。

derive 宏只能应用在结构体和枚举类型上。

创建 hello_macro 项目

1cargo new hello_macro

Cargo.toml 文件中配置:

1[dependencies]
2quote = "1.0"
3syn = "1.0"
4
5[lib]
6proc-macro = true

src 目录下新建 lib.rs 文件,并添加以下内容:

 1extern crate proc_macro;
 2
 3use proc_macro::TokenStream;
 4use quote::quote;
 5use syn;
 6
 7#[proc_macro_derive(HelloMacro)]
 8pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
 9    // Construct a representation of Rust code as a syntax tree
10    // that we can manipulate
11    let ast = syn::parse(input).unwrap();
12
13    // Build the trait implementation
14    impl_hello_macro(&ast)
15}
16
17fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
18    let name = &ast.ident;
19    let gen = quote! {
20        impl HelloMacro for #name {
21            fn hello_macro() {
22                println!("Hello, Macro! My name is {}!", stringify!(#name));
23            }
24        }
25    };
26    gen.into()
27}

main.rs 中使用上面定义的程序宏:

 1use hello_macro::HelloMacro;
 2
 3pub trait HelloMacro {
 4    fn hello_macro();
 5}
 6
 7#[derive(HelloMacro)]
 8struct Pancakes;
 9
10fn main() {
11    Pancakes::hello_macro();
12}

Attribute macros

Attribute 宏的定义与定义函数类似,需要添加 proc_macro_attribute 属性。

示例

创建 my_macro 项目

1cargo new my_macro

Cargo.toml 文件中添加:

1[lib]
2proc-macro = true

src 目录下新建 lib.rs 文件,并添加以下内容:

1extern crate proc_macro;
2use proc_macro::TokenStream;
3
4#[proc_macro_attribute]
5pub fn show_streams(attr: TokenStream, item: TokenStream) -> TokenStream {
6    println!("attr: \"{}\"", attr.to_string());
7    println!("item: \"{}\"", item.to_string());
8    item
9}

main.rs 中调用上面定义的程序宏:

 1extern crate my_macro;
 2
 3use my_macro::show_streams;
 4
 5#[show_streams]
 6fn invoke1() {}
 7
 8#[show_streams(bar)]
 9fn invoke2() {}
10
11#[show_streams(multiple => tokens)]
12fn invoke3() {}
13
14#[show_streams { delimiters }]
15fn invoke4() {}
16
17fn main() {
18    invoke1();
19    invoke2();
20    invoke3();
21    invoke4();
22}

宏的调用

可以在下面这些场景中调用宏:

  • Expressions and statements
  • Patterns
  • Types
  • Items including associated items
  • macro_rules transcribers
  • External blocks

示例

 1// Used as an expression.
 2let x = vec![1,2,3];
 3
 4// Used as a statement.
 5println!("Hello!");
 6
 7// Used in a pattern.
 8macro_rules! pat {
 9    ($i:ident) => (Some($i))
10}
11
12if let pat!(x) = Some(1) {
13    assert_eq!(x, 1);
14}
15
16// Used in a type.
17macro_rules! Tuple {
18    { $A:ty, $B:ty } => { ($A, $B) };
19}
20
21type N2 = Tuple!(i32, i32);
22
23// Used as an item.
24thread_local!(static FOO: RefCell<u32> = RefCell::new(1));
25
26// Used as an associated item.
27macro_rules! const_maker {
28    ($t:ty, $v:tt) => { const CONST: $t = $v; };
29}
30trait T {
31    const_maker!{i32, 7}
32}
33
34// Macro calls within macros.
35macro_rules! example {
36    () => { println!("Macro call in a macro!") };
37}
38// Outer macro `example` is expanded, then inner macro `println` is expanded.
39example!();

相关资料

Macros - The Rust Programming Language

Macros - The Rust Reference

macro_rules! - Rust By Example

The Little Book of Rust Macros