ch19-06-macros.md
commit 95e931170404cb98d476b19017cbbdbc00d0834d

我们已经在本书中使用过像 println! 这样的宏了,不过还没完全探索什么是宏以及它是如何工作的。Macro)指的是 Rust 中一系列的功能:使用 macro_rules!声明Declarative)宏,和三种 过程Procedural)宏:

  • 自定义 #[derive] 宏在结构体和枚举上指定通过 derive 属性添加的代码
  • 类属性(Attribute-like)宏定义可用于任意项的自定义属性
  • 类函数宏看起来像函数不过作用于作为参数传递的 token

我们会依次讨论每一种宏,不过首要的是,为什么已经有了函数还需要宏呢?

宏和函数的区别

从根本上来说,宏是一种为写其他代码而写代码的方式,即所谓的 元编程metaprogramming)。在附录 C 中会探讨 derive 属性,其生成各种 trait 的实现。我们也在本书中使用过 println! 宏和 vec! 宏。所有的这些宏以 展开 的方式来生成比你所手写出的更多的代码。

元编程对于减少大量编写和维护的代码是非常有用的,它也扮演了函数扮演的角色。但宏有一些函数所没有的附加能力。

一个函数签名必须声明函数参数个数和类型。相比之下,宏能够接收不同数量的参数:用一个参数调用 println!("hello") 或用两个参数调用 println!("hello {}", name) 。而且,宏可以在编译器翻译代码前展开,例如,宏可以在一个给定类型上实现 trait。而函数则不行,因为函数是在运行时被调用,同时 trait 需要在编译时实现。

实现宏不如实现函数的一面是宏定义要比函数定义更复杂,因为你正在编写生成 Rust 代码的 Rust 代码。由于这样的间接性,宏定义通常要比函数定义更难阅读、理解以及维护。

宏和函数的最后一个重要的区别是:在一个文件里调用宏 之前 必须定义它,或将其引入作用域,而函数则可以在任何地方定义和调用。

使用 macro_rules! 的声明宏用于通用元编程

Rust 最常用的宏形式是 声明宏declarative macros)。它们有时也被称为 “macros by example”、“macro_rules! 宏” 或者就是 “macros”。其核心概念是,声明宏允许我们编写一些类似 Rust match 表达式的代码。正如在第六章讨论的那样,match 表达式是控制结构,其接收一个表达式,与表达式的结果进行模式匹配,然后根据模式匹配执行相关代码。宏也将一个值和包含相关代码的模式进行比较;此种情况下,该值是传递给宏的 Rust 源代码字面值,模式用于和前面提到的源代码字面值进行比较,每个模式的相关代码会替换传递给宏的代码。所有这一切都发生于编译时。

可以使用 macro_rules! 来定义宏。让我们通过查看 vec! 宏定义来探索如何使用 macro_rules! 结构。第八章讲述了如何使用 vec! 宏来生成一个给定值的 vector。例如,下面的宏用三个整数创建一个 vector:

#![allow(unused)]
fn main() {
let v: Vec<u32> = vec![1, 2, 3];
}

也可以使用 vec! 宏来构造两个整数的 vector 或五个字符串 slice 的 vector。但却无法使用函数做相同的事情,因为我们无法预先知道参数值的数量和类型。

在示例 19-28 中展示了一个 vec! 稍微简化的定义。

文件名:src/lib.rs

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

示例 19-28: 一个 vec! 宏定义的简化版本

注意:标准库中实际定义的 vec! 包括预分配适当量的内存的代码。这部分为代码优化,为了让示例简化,此处并没有包含在内。

#[macro_export] 注解表明只要导入了定义这个宏的 crate,该宏就应该是可用的。如果没有该注解,这个宏不能被引入作用域。

接着使用 macro_rules! 和宏名称开始宏定义,且所定义的宏并 不带 感叹号。名字后跟大括号表示宏定义体,在该例中宏名称是 vec

vec! 宏的结构和 match 表达式的结构类似。此处有一个分支模式 ( $( $x:expr ),* ) ,后跟 => 以及和模式相关的代码块。如果模式匹配,该相关代码块将被执行。这里这个宏只有一个模式,那就只有一个有效匹配方向,其他任何模式方向(译者注:不匹配这个模式)都会导致错误。更复杂的宏会有多个分支模式。

宏定义中有效模式语法和在第十八章提及的模式语法是不同的,因为宏模式所匹配的是 Rust 代码结构而不是值。回过头来检查下示例 19-28 中模式片段什么意思。对于全部的宏模式语法,请查阅 Rust 参考

首先,一对括号包含了整个模式。我们使用美元符号($)在宏系统中声明一个变量来包含匹配该模式的 Rust 代码。美元符号明确表明这是一个宏变量而不是普通 Rust 变量。之后是一对括号,其捕获了符合括号内模式的值用以在替代代码中使用。$() 内则是 $x:expr ,其匹配 Rust 的任意表达式,并将该表达式命名为 $x

$() 之后的逗号说明一个可有可无的逗号分隔符可以出现在 $() 所匹配的代码之后。紧随逗号之后的 * 说明该模式匹配零个或更多个 * 之前的任何模式。

当以 vec![1, 2