0
点赞
收藏
分享

微信扫一扫

rust 宏

我们已经在本书中使用过像 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:

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] 注解说明宏应该是可用的。如果没有该注解,这个宏不

能被引入作用域。

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

义体,在该例中宏名称是 vec 。

vec! 宏的结构和 match 表达式的结构类似。此处有一个单边模式 ( $( $x:expr ),* ) ,后跟 => 以及和

模式相关的代码块。如果模式匹配,该相关代码块将被执行。假设这是这个宏中唯一的模式,则只有这

一种有效匹配,其他任何匹配都是错误的。更复杂的宏会有多个单边模式。

宏定义中有效模式语法和在第十八章提及的模式语法是不同的,因为宏模式所匹配的是 Rust 代码结构

而不是值。回过头来检查下示例 19-28 中模式片段什么意思。对于全部的宏模式语法,请查阅参考。

首先,一对括号包含了整个模式。接下来是美元符号($ ),后跟一对括号,捕获了符合括号内模式的值

以用于替换后的代码。$ () 内则是 $x:expr ,其匹配 Rust 的任意表达式,并将该表达式记作 $x。

$ () 之后的逗号说明一个可有可无的逗号分隔符可以出现在 $ () 所匹配的代码之后。紧随逗号之后的 * 说

明该模式匹配零个或更多个 * 之前的任何模式。

当以 vec![1, 2, 3]; 调用宏时,$x 模式与三个表达式 1、2 和 3 进行了三次匹配。

现在让我们来看看与此单边模式相关联的代码块中的模式:对于每个(在 => 前面)匹配模式中的 $ () 的

部分,生成零个或更多个(在 => 后面)位于 $()* 内的 temp_vec.push() ,生成的个数取决于该模式被

匹配的次数。$x 由每个与之相匹配的表达式所替换。当以 vec![1, 2, 3]; 调用该宏时,替换该宏调用所

生成的代码会是下面这样:

{

let mut temp_vec = Vec::new();

temp_vec.push(1);

temp_vec.push(2);

temp_vec.push(3);

temp_vec

}

我们已经定义了一个宏,其可以接收任意数量和类型的参数,同时可以生成能够创建包含指定元素的

vector 的代码。

macro_rules! 中有一些奇怪的地方。在将来,会有第二种采用 macro 关键字的声明宏,其工作方式类

似但修复了这些极端情况。在此之后,macro_rules! 实际上就过时(deprecated)了。在此基础之上,

同时鉴于大多数 Rust 程序员 使用宏而非 编写宏的事实,此处不再深入探讨 macro_rules!。请查阅在线

文档或其他资源,如 ”The Little Book of Rust Macros” 来更多地了解如何写宏。

举报

相关推荐

0 条评论