Rust 中的宏 macro

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

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

 

1. 宏与函数的区别

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

 

2. 使用宏的好处

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

 

3. 宏的定义

宏的定义有两种方式:

  • 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。

声明性宏的定义方式:

macro_rules! $name {
    $rule0 ;
    $rule1 ;
    // …
    $ruleN ;
}

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

示例:

// 定义一个生成函数的宏
macro_rules! create_function {
    ($func_name:ident) => (
        fn $func_name() {
            println!("function {:?} is called", stringify!($func_name))
        }
    )
}

fn main() {
    // 调用之前定义的宏
    create_function!(foo); // 也可以这样:create_function![foo];
    foo();
}

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

宏名字的解析与函数略微有些不同,宏的定义必须出现在宏调用之前,而 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 是重复运算符。

示例:

#[macro_export]
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

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

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

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

程序宏

程序宏有三种形式:

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

Function-like macros

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

示例:

创建 macro_demo 项目

cargo new macro_demo

在 Cargo.toml 文件中添加:

[lib]
proc-macro = true

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

extern crate proc_macro; // 这是 Rust 提供的用来处理程序宏的
use proc_macro::TokenStream;

#[proc_macro]
pub fn make_answer(_item: TokenStream) -> TokenStream {
    "fn answer() -> u32 { 42 }".parse().unwrap()
}

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

extern crate macro_demo;
use macro_demo::make_answer;

make_answer!();

fn main() {
    println!("{}", answer());
}

Derive macros

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

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

创建 hello_macro 项目

cargo new hello_macro

在 Cargo.toml 文件中配置:

[dependencies]
quote = "1.0"
syn = "1.0"

[lib]
proc-macro = true

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

extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;
use syn;

#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    // Construct a representation of Rust code as a syntax tree
    // that we can manipulate
    let ast = syn::parse(input).unwrap();

    // Build the trait implementation
    impl_hello_macro(&ast)
}

fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;
    let gen = quote! {
        impl HelloMacro for #name {
            fn hello_macro() {
                println!("Hello, Macro! My name is {}!", stringify!(#name));
            }
        }
    };
    gen.into()
}

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

use hello_macro::HelloMacro;

pub trait HelloMacro {
    fn hello_macro();
}

#[derive(HelloMacro)]
struct Pancakes;

fn main() {
    Pancakes::hello_macro();
}

Attribute macros

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

示例:

创建 my_macro 项目

cargo new my_macro

在 Cargo.toml 文件中添加:

[lib]
proc-macro = true

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

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
}

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

extern crate my_macro;

use my_macro::show_streams;

#[show_streams]
fn invoke1() {}

#[show_streams(bar)]
fn invoke2() {}

#[show_streams(multiple => tokens)]
fn invoke3() {}

#[show_streams { delimiters }]
fn invoke4() {}

fn main() {
    invoke1();
    invoke2();
    invoke3();
    invoke4();
}

 

4. 宏的调用

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

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

示例:

// Used as an expression.
let x = vec![1,2,3];

// Used as a statement.
println!("Hello!");

// Used in a pattern.
macro_rules! pat {
    ($i:ident) => (Some($i))
}

if let pat!(x) = Some(1) {
    assert_eq!(x, 1);
}

// Used in a type.
macro_rules! Tuple {
    { $A:ty, $B:ty } => { ($A, $B) };
}

type N2 = Tuple!(i32, i32);

// Used as an item.
thread_local!(static FOO: RefCell<u32> = RefCell::new(1));

// Used as an associated item.
macro_rules! const_maker {
    ($t:ty, $v:tt) => { const CONST: $t = $v; };
}
trait T {
    const_maker!{i32, 7}
}

// Macro calls within macros.
macro_rules! example {
    () => { println!("Macro call in a macro!") };
}
// Outer macro `example` is expanded, then inner macro `println` is expanded.
example!();

 

5. 相关资料

Macros - The Rust Programming Language

Macros - The Rust Reference

macro_rules! - Rust By Example

The Little Book of Rust Macros

std 是 Rust 标准函数库,env 模块提供了处理环境函数。在使用标准函数库的时候,使用 use 导入相应的 module。  一、直接输出 use std::env;fn main(){ for ...