Rust for cpp dev - 宏

宏是一种可以生成代码的代码,这种形式被称为“元编程”(metaprogramming)。我们已经使用过 Rust 中的多个宏,例如 println!, vec!, #[derive]等。这些宏都会在编译预处理时展开来生成代码。

Rust 中的宏有多个种类,最常用的是“声明型宏”(declarative macros),此外还有三种“过程型宏”(procedural macros):

  • 通过 #[derive] 为结构体或者枚举类型添加代码
  • 属性
  • 函数

声明型宏

声明型宏会比较传入的表达式是否符合格式要求,如果符合则用传入的表达式替换宏中的 placeholder。

例如,我们仿照 vec! 宏实现一个简单的 my_vec! 宏:

// brings macro into the scope
#[macro_export]

macro_rules! my_vec {
    // similar to "match" expression
    ( $( $x:expr ),* ) => {
        // if pattern matches, below code is emitted
        {  // this "{}" is needed because the macro will be used in a one-line expression
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

使用方式和 vec! 一致:

use macros::my_vec;

fn main() {
    let v = my_vec![1, 3, 5];
    println!("Hello, {:?}!", v);
}

现在来分析代码中的关键点。

  • 类似于 match 的结构,根据不同的模式生成不同代码,上面只匹配一种模式

  • ( $( $x:expr ),* ) 是一种模式。expr 指 Rust 表达式,这里将匹配 Rust 表达式并赋值给 $x 变量,, 就是指 , 字符,* 表示可以出现任意次或者不出现。基本类似于正则表达式。结果是,当我们调用 my_vec![1, 3, 5] 时,$x 匹配到了 1, 3, 5

  • $(temp_vec.push($x);)* 表明,模式匹配成功多少次,就对 $x 调用多少次 temp_vec.push($x)。对于 my_vec![1, 3, 5],生成的代码是:

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

macro_rules! 有一些 edge case 不好处理,Rust 会在将来用更好的实现代替它。实际上,对于大部分的开发者而言,Rust 鼓励大家去使用 macro 而不是自己编写 macro。

过程型宏

(编写中)

推荐阅读更多精彩内容