使用 macro_rules 编写生产 Rust 宏!

如果您对宏不熟悉,那么您对 ​​Rust 也不熟悉。

在本指南中,我们将揭秘支撑数十万个 Rust 应用程序的三个宏。它们不是由巫师编写的,而是由我们渴望成为的才华横溢的 Rust 工程师编写的。

这三个宏都是 "声明式 "的,也就是用宏规则(macro_rules! 这些宏构成了 Rust 宏的主体,将标记作为输入,进行模式匹配,并根据匹配结果输出源代码。 程序宏构成了宏家族的其他部分,涉及对标记流的编程操作。

一个简单的 Rust 宏:assert_eq!

let a = 3;  
let b = 1 + 2;  
assert_eq!(a, b);
 
assert_eq!(a, b, "we are testing addition with {} and {}", a, b);

assert_eq!支持两种参数模式。

  • 第一种匹配两个表达式,a, b并比较它们的值是否相等。
  • 第二种也接受格式字符串和任意数量的参数进行插值。

如果任一匹配模式的相等性检查失败,assert_eq!则会产生panic。

因为我们有两种参数模式,所以你正确地期望宏定义中有两条规则:

#[macro_export]
macro_rules! assert_eq {
    ($left:expr, $right:expr $(,)?) => { ... };

    ($left:expr, $right:expr, $($arg:tt)+) => { ... };

}
  • 第一个宏:采用两个表达式 - 等式的左边和右边 - 以及可选的尾随逗号。
  • 第二个宏:规则 2 匹配同样的两个表达式,但也匹配与格式字符串及其参数相对应的一个或多个TokenTree 令牌树。
TokenTree :可以是单个标记(如 3 或 "hello"),也可以是由()、[] 或 {} 包含的任意数量的嵌套标记。

它们的扩展几乎完全相同:

#[macro_export]
macro_rules! assert_eq {  
    ($left:expr, $right:expr $(,)?) => {  
        match (&$left, &$right) {  
            (left_val, right_val) => {

                if !(*left_val == *right_val) {

                    let kind = $crate::panicking::AssertKind::Eq;

                    // ...
                }  
            }  
        }  
    };  
    ($left:expr, $right:expr, $($arg:tt)+) => {  
        match (&$left, &$right) {  
            (left_val, right_val) => {  
                if !(*left_val == *right_val) {  
                    let kind = $crate::panicking::AssertKind::Eq;  
                    // ...
                }  
            }  
        }  
    };  
}

这两条规则产生的代码都会对左手和右手表达式进行求值,并将输出结果的引用存储在 left_val 和 right_val 3 中。 这样,我们就不必多次求值 $left 或 $right。

请记住,虽然 assert_eq! 例子使用了原始类型,但 $left 和 $right 可能是昂贵的函数调用。

接下来,使用 PartialEq 4 进行简单的相等性检查。 当检查失败时,事情就变得有趣了。

两种规则都将 $crate::panicking::AssertKind::Eq 赋值给变量 kind 5 。

Rust 很聪明,这个宏本地的 kind 变量不会与任何调用 assert_eq! 的名为 kind 的变量发生冲突,这就是所谓的宏卫生(macro hygiene)特性。

但为什么要使用完全限定的模块路径来指定 AssertKind::Eq 呢? 当你使用 #[macro_export] 导出宏时,并不能保证在使用该宏时的作用域中会出现什么。

assert_eq! 的用户不应该被要求自己导入这个实现细节,而完全限定的路径可以避免这一点。

... 更多点击标题