Rust 支持多年来发展了相当多的宏系统。Rust 宏的一个显著特点是, 保证它们不会意外引用其范围之外的标识符, 因此 rust 中的宏实现是“卫生”的。正如人们所期望的那样, Rust 宏在编译之前被扩展到源代码, 并与翻译单元一起编译。编译器对扩展的宏强制执行范围规则,以使其保持卫生。Rust宏不同于其他构造,因为它们总是以感叹号结尾 !

现代 Rust 有两种使用宏的方法;较旧的语法方法和较新的程序宏方式。让我们来看看这些:

语法宏

pre-1.0 发布以来, 宏系统一直作为 Rust 的一部分存在。这些宏是使用 macro_rules! 宏定义的。让我们看一个例子:

// code/chapter2/syntactic-macro.rs

macro_rules! factorial {
    ($x:expr) => {{
        let mut result = 1;
        for i in 1..($x + 1) {
            result = result * i;
        }
        result
    }};
}

fn main() {
    let arg = std::env::args()
        .nth(1)
        .expect("Please provide only one argument");

    println!(
        "{:?}",
        factorial!(arg.parse::<u64>().expect("Could not parse to an integer"))
    );
}

我们从定义阶乘宏开始。由于我们不希望编译器拒绝编译我们的代码, 因为它可能会溢出宏堆栈, 我们将使用非递归实现。Rust中的语法宏是一组规则,其中左侧指示规则应如何与输入匹配,右侧指示规则应扩展到什么。规则通过 => 运算符映射到右侧的表达式。使用 $ 符号声明规则的局部变量。匹配规则是用一种特殊的宏语言来表示的,它有自己的一组保留关键字。 我们的声明说,我们希望采用任何有效的 rust 表达式;在这种特定情况下,它的值应该是整数。我们会把它留给调用者,以确保这是真的。然后,我们在累积结果的同时循环从1到范围内的最后一个整数。完成后,我们使用隐式返回语法返回结果。

我们的调用方是 main 函数, 因为我们使用 std::env 模块从用户处获取输入。如果没有输入, 我们将获取输入列表中的第一个, 并引发错误。然后, 我们打印宏的结果, 并尝试将输入解析为 u64 , 然后再传递给它。我们还处理分析可能失败的情况。

# rustc syntactic-macro.rs && ./syntactic-macro 5 
120

Rust 还提供了一些用于调试宏的工具。人们可能有兴趣看看展开的宏是什么样子。trace_macros! 宏正是这样做的。为了使其工作, 我们将需要启用一个功能门, 如下面的代码段所示 (因为它在 Rust 中还不稳定, 这个代码将只在 Rust nightly 有效):

#![feature(trace_macros)] 
trace_macros!(true);

请注意, 扩展还包括 println! 因为它是标准库中定义的宏。

同样的扩展也可以使用以下命令来调用编译器: rustc -Z unstable-options --pretty expanded syntacticmacro.rs.

过程宏

虽然常规语法宏在许多情况下都很有用, 但某些应用程序需要高级代码生成功能, 而这些功能最好使用编译器操作的 AST 来完成。因此, 有必要扩大宏系统, 将其包括在内。后来决定, 旧的宏系统和这个称为 过程宏 的新系统将共存。随着时间的推移, 这是为了取代语法宏系统。编译器支持从外部 crates 加载插件;这些插件可以在编译器生成后接收AST。有可用的API来修改AST,以根据需要添加新代码。对这个系统的详细讨论超出了本书的范围。