@@ -43,7 +43,7 @@ let v: Vec<u32> = vec! [1, 2, 3];
下面清单 20-35 展示了 `vec!` 宏的略微简化的定义。
<a name="listing_20-35"></a>
文件名:`src/lib .rs`
文件名:`projects/declarative_macro/src/main .rs`
``` rust
#[ macro_export ]
@@ -84,7 +84,7 @@ macro_rules! vec {
当我们通过 `vec! [1, 2, 3];` 调用这个宏时,`$x` 模式会与三个表达式 `1` 、`2` 和 `3` 匹配三次。
现在来看看与这个支臂关联的代码体中的模式:对于匹配了模式中 `$()` 的各个部分,根据该模式匹配的次数, `$()*` 里 的 `temp_vec.push()` 会被 零次或更 多次生成 。其中的 `$x` 会被各 个匹配的表达式替换。当咱 们以 `vec! [1, 2, 3];` 调用这个宏时,所 生成的替换这个宏的代码,将是下面这样 :
现在我们 来看看与这个支臂关联的代码主 体中的模式:`$()*` 内 的 `temp_vec.push()` 会针对匹配模式中的 `$()` 每个部分,而生成 零次或多次,具体取决于该模式匹配的次数 。其中 `$x` 以每 个匹配的表达式替换。当我 们以 `vec! [1, 2, 3];` 调用这个宏时,生成的替换这次宏调用的代码将如下 :
``` rust
{
@@ -96,47 +96,47 @@ macro_rules! vec {
}
```
咱们就已定义了可 取任意数目 、任意类型参数,并能生成创建出包含这些特 定元素矢量的一个宏了 。
我们定义了一个宏,他可以 取任意数量 、任意类型的 参数,并且可以生成代码来创建一个包含指 定元素的 矢量。
要了解有关如何编写宏的更多信息,请查阅在线文档或其他资源,比如由 Daniel Keep 撰写, Lukas Wirth 接续编写的 [The Little Book of Rust Macros ](https://veykril.github.io/tlborm/ )。
要了解更多有关如何编写宏的知识,请参考在线文档或其他资源,比如由 Daniel Keep 起头, Lukas Wirth 续写的 [“Rust 宏小册子” ](https://veykril.github.io/tlborm/ )。
## 用于根据属性生成代码的过程宏
宏的第二种形式是过程宏, 其行为更像是函数( 并且属于一种过程, a type of procedure) 。*过程宏, procedural macros* 接受一些代码作为输入,对该代码操作,并生成一些代码作为输出,而非像声明式宏那样,与模式匹配并以其他代码替换代码。过程宏的三种类别分别是
## 用于从属性生成代码的程序性宏
- 自定义的 `derive` 宏、
- 类属性宏, attribute-like macros、
- 及类函数宏, function-like macros。
**Procedural Macros for Generating Code from Attributes **
并且三种都以类似方式工作。
在创建过程宏时, 其定义必须位于一个有着特殊代码箱类型的他们自己的代码箱中。这是出于一些复杂的技术原因, 我们( Rust 开发团队)希望今后能消除这些原因。在下面清单 20-36 中,我们展示了怎样定义一个过程宏,其中 `some_attribute` 是使用特定宏变种的占位符。
宏的第二种形式,便是 * 程序性宏, procedural macro * , 其行事更像函数( 而是程序的一种类型, a type of procedure) 。程序性宏接收一些代码作为输入, 在那些代码上加以操作, 并产生作为输出的一些代码, 而如同非声明式宏所做的那样, 与一些模式匹配并以别的代码替换那些代码。程序性宏的三种类别分别是定制派生宏, custom derive、类属性宏, attribute-like 及类函数宏, function-like, 且这三种类别的程序性宏, 都以类似方式运作。
在创建程序性宏时, 那些定义务必要位处有着特别代码箱名字的他们自己的代码箱中。这是由于咱们( Rust 开发团队)希望在今后消除的一些复杂技术原因。在下面清单 19-29 中,咱们给出了如何定义一个程序性宏的方式,其中 `some_attribute` 是为使用某个特定宏变种的一个占位符。
<a name="listing_20-36"></a>
文件名:`src/lib.rs`
``` rust
use proc_macro ;
use proc_macro ::TokenStream ;
#[ some_attribute ]
pub fn some_name ( input : TokenStream ) -> TokenStream {
}
```
* 清单 19-29: 定义某个程序性宏的示例 *
**清单 20-36 ** : 定义过程宏的示例
定义过程宏的函数,会取一个 `TokenStream` 值作为输入,并生成一个 `TokenStream` 作为输出。`TokenStream` 类型由 Rust 附带的 `proc_macro` 代码箱定义, 表示令牌序列, a sequence of tokens。这是这种宏的核心: 宏所操作的源代码构成输入的 `TokenStream` ,而宏生成的代码则是输出的 `TokenStream` 。该函数附带了一个属性,指定我们正在创建何种类别的过程宏。我们可以在同一个代码箱中包含多种类别的过程宏。
我们来看看不同类别的过程宏。我们将从自定义 `derive` 宏开始,然后探讨使其他形式有所不同的细微差异。
这个定义了某个宏的函数,会取一个 `TokenStream` 值作为输入,并产生出一个 `TokenStream` 作为输出。`TokenStream` 类型是由 Rust 所包含的 `proc_macro` 代码箱定义, 且表示的是一个令牌序列, a sequence of tokens。这个宏的核心如此: 该宏在其上操作的源代码, 构成了那个输入的 `TokenStream` ,而该宏产生的代码,便是那个输出的 `TokenStream` 。该函数还有一个附加给他的属性,指出咱们正在创建的是何种的程序性宏。在同一代码箱中,咱们可以有着多种类别的程序性宏。
## 自定义 `derive` 宏
下面就来看看各种不同类别的程序性宏。咱们将以一个定制的派生宏开始,并于随后探讨令到其他那些宏形式有所区别的一些小差异 。
我们来创建一个名为 `hello_macro` 的代码箱,他通过一个名为 `hello_macro` 的关联函数,定义了个名为 `HelloMacro` 的特质。与其让用户为他们的每个类型都实现 `HelloMacro` 特质,我们提供一个过程宏,以便用户可以通过 `[derive(HelloMacro)]` 来注解他们的类型,以获得 `hello_macro` 函数的默认实现。默认实现将打印 `你好,宏!我的名字是 TypeName!` ,其中的 `TypeName` 是该特质被定义所在的类型的名字。换句话说,我们将编写一个代码箱,使其他程序员能够编写如下清单 20-37 中的代码 。
## 怎样编写出定制的 `derive` 宏
**How to Write a Custom `derive` Macro **
咱们就来创建一个名为 `hello_macro` 的宏,这个宏定义了一个名为 `HelloMacro` ,有着名为 `hello_macro` 的关联函数的特质。与让咱们的用户为他们的各个类型实现这个 `HelloMacro` 特质不同,咱们将提供一个程序性宏,如此用户就可以 `[derive(HelloMacro)]` 注解他们的类型,从而得到那个 `hello_macro` 函数的默认实现。默认实现将打印出 `你好,宏!我的名字是 TypeName!` ,其中的 `TypeName` 是这个特质被定义所在类型的名字。也就是说,咱们将编写一些实现其他编程者编写如下清单 19-30 中用到咱们代码箱的代码。
文件名:`src/main.rs`
<a name="listing_20-37"></a>
文件名:`projects/pancakes/src/main.rs`
``` rust
use hello_macro ::HelloMacro ;
@@ -150,15 +150,18 @@ fn main() {
}
```
当我们完成编写时,此代码将打印 `你好,宏!我的名字叫 Pancakes! ` 。第一步是要构造一个新的库代码箱,像下面这样:
**清单 20-37 ** :我们的代码箱用户在使用我们的过程宏时,将能够编写的代码
当我们完成时,这段代码将打印 `你好,宏!我的名字叫 Pancakes! ` 。第一步是构造一个新的库代码箱,如下所示:
``` console
$ cargo new hello_macro --lib --vcs none
$ cargo new hello_macro --lib
```
接下来,咱 们将定义那个 `HelloMacro` 特质及其关联函数:
接下来,在清单 20-38 中,我 们将定义 `HelloMacro` 特质及其关联函数。
文件名:`src/lib.rs`
<a name="listing_20-38"></a>
文件名:`projects/hello_macro/src/lib.rs`
``` rust
pub trait HelloMacro {
@@ -166,7 +169,12 @@ pub trait HelloMacro {
}
```
咱们就有了一个特质及其函数。到这里,咱们代码箱的用户就可以实现这个特质来达成所需功能,像下面这样:
**清单 20-38 ** :一个我们将与 `derive` 宏一起使用的简单特质
我们有一个特质及其函数。此时,我们的代码箱用户可以实现这个特质,以实现所需的功能,如下清单 20-39 中所示。
<a name="listing_20-39"></a>
文件名:`src/main.rs`
``` rust
use hello_macro ::HelloMacro ;
@@ -184,33 +192,48 @@ fn main() {
}
```
**清单 20-39 ** :当用户编写 `HelloMacro` 特质的手动实现时,会是什么样子
然而,他们需要针对他们打算与 `hello_macro` 一起使用的每种类型,都编写实现代码块;我们希望省去他们这部分工作。
此外,我们还无法为 `hello_macro` 函数提供将打印对其实现该特质的类型的名字的默认实现: Rust 不具备反射能力,因此无法在运行时查找类型的名字。我们需要一个宏在编译时生成代码。
> **译注**:关于反射能力/机制,请参考:
>
> - [Reflection in Java](https://www.geeksforgeeks.org/java/reflection-in-java/)
>
> - [Java中的反射](https://java.xfoss.com/Ch20_Appendix_B.html#java%E4%B8%AD%E7%9A%84%E5%8F%8D%E5%B0%84)
不过,用户们将需要为各种打算使用 `hello_macro` 特质的类型,编写那个实现的代码块;而咱们原本是要他们免于必须完成这项工作的。
此外,咱们尚不能提供,有着将打印特质被实现在其上类型名字的`hello_macro` 函数默认实现: Rust 没有反射能力, reflection capabilities, 因此他无法在运行时查找处那个类型的名字。咱们需要一个宏, 从而在编译时生成代码。
下一步就是要定义这个程序性宏。在编写这个小节的时候,程序性宏是需要在他们 自己的代码箱中的 。最终这个 限制可能会被消除。代码箱的结构 组织与 宏代码箱方面的约定如下:对于名为 `foo` 的代码箱,那么定制派生程序性宏代码箱就会叫做 `foo_derive` 。下面就在咱们的 `hello_macro` 项目内,开 启一个名为 `hello_macro_derive` 的新代码箱:
下一步是定义过程宏。在撰写本文时,过程宏需要位于 自己的代码箱中。最终,这一 限制可能会被取消。组织代码箱和 组织宏代码箱方面的约定如下:对于名为 `foo` 的代码箱,则自定义的 `derive` 过程宏代码箱应名为 `foo_derive` 。我们来在 `hello_macro` 项目内,启动 一个名为 `hello_macro_derive` 的新代码箱:
``` console
$ cargo new hello_macro_derive --lib --vcs none
$ cargo new hello_macro_derive
```
咱们的 这两个代码箱是密切 相关的 ,因此咱们是在咱们的 `hello_macro` 代码箱目录下, 创建的 这个程序性 宏代码箱。而若咱 们修改了 `hello_macro` 中的特质定义,咱们就将不得不也要 修改 `hello_macro_derive` 中那个程序性 宏。两个代码箱将 需要单独发布,且 使用这两个代码箱的程序员,将需要将二者都添加 为依赖,并同时把他们都带入到作用域。相反,咱们 可以让 `hello_macro` 代码箱,将 `hello_macro_derive` 作为依赖使用 ,并重导出这些程序性 宏的代码。然而,咱们阻止结构该 项目的这种 方式,会让那些 不想要 `derive` 功能的程序员 ,也可以使用 `hello_macro` 。
这两个代码箱紧密 相关,因此我们在 `hello_macro` 代码箱目录下创建这个过 程宏代码箱。当我 们修改 `hello_macro` 中的特质定义时,也必须 修改 `hello_macro_derive` 中的过程 宏。这 两个代码箱需要单独发布,而 使用这两个代码箱的程序员则需要添加他们 为依赖项 ,并带入他们到作用域。我们也 可以让 `hello_macro` 代码箱作为依赖项使用 `hello_macro_derive` ,并重新 导出过程 宏的代码。然而,我们组织 项目的方式,让程序员即使 不想要 `derive` 的 功能,也可以使用 `hello_macro` 。
咱 们需要将 `hello_macro_derive` 代码箱,声明为程序性宏的代码箱。如同马上就会看到的那样,咱 们还需要来自 `syn` 与 `quote` 代码箱的功能,, 因此咱们就需要将他们添加为依赖。请将下面的配置,添加 到 `hello_macro_derive` 的 `Cargo.toml` 文件:
我 们需要声明 `hello_macro_derive` 代码箱为一个过程宏的代码箱。稍后咱们就会看到,我 们还需要 `syn` 和 `quote` 代码箱中 的功能,因此我们需要添加他们为依赖。请添加以下内容 到 `hello_macro_derive` 的 `Cargo.toml` 文件:
文件名:`projects/hello_macro/hello_macro_derive/Cargo.toml`
``` toml
[ lib ]
proc-macro = true
[ dependencies ]
syn = "1 .0"
syn = "2 .0"
quote = "1.0"
```
要开始定义这个程序性宏,就要将下面清单 19-31 中的代码,放置于 `hello_macro_derive` 代码箱的 `src/lib.rs` 文件中。请注意在咱 们添加了 `impl_hello_macro` 函数定义前,此 代码不会编译。
要开始定义这个过程宏,请要下面清单 20-40 中的代码放入 `hello_macro_derive` 代码箱的 `src/lib.rs` 文件中。请注意,在我 们添加 `impl_hello_macro` 函数的 定义之 前,这段 代码不会编译。
文件名:`hello_macro_derive/src/lib.rs`
<a name="listing_20-40"></a>
文件名:`projects/hello_macro/hello_macro_derive/src/lib.rs`
``` rust
use proc_macro ::TokenStream ;
@@ -219,28 +242,42 @@ use syn;
#[ proc_macro_derive(HelloMacro) ]
pub fn hello_macro_derive ( input : TokenStream ) -> TokenStream {
// 以语法树形式,构建出咱们可操作 Rust 代码的表示
// 将 Rust 代码构造为我们可以操作的语法树形式
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn ::parse ( input ) . unwrap ( ) ;
// 构造出这个 特质实现
// 构建 特质实现
// Build the trait implementation
impl_hello_macro ( & ast )
}
```
* 清单 19-31: 多数程序性宏为 处理 Rust 代码而 都需要的代码*
**清单 20-40 ** :大多数过程宏代码箱为了 处理 Rust 代码都需要的代码
请注意咱们已经代码分解到 `hello_macro_derive` 函数中,由其负责解析那个 `TokenStream` ,而其中的 `impl_hello_macro` 函数,则负责转换那个语法树:这样做令到编写程序性宏更为方便。对于几乎每个咱们所见到的或创建的程序性宏,外层函数(此示例中的 `hello_macro_derive` )中的代码将是一致的。而咱们在那个内层函数(此示例中的 `impl_hello_macro` )中指定的代码,将依据咱们程序性宏目的而有所不同。
请注意,我们拆分了代码为两个函数:
咱们引入了三个新的代码箱:`proc_macro` 、[`syn` ](https://crates.io/crates/syn ) 与 [`quote` ](https://crates.io/crates/quote )。`proc_macro` 代码箱是 Rust 自带的,因此咱们无需将其添加到 `Cargo.toml` 的依赖。`proc_macro` 代码箱,是实现从咱们的代码读取及操作 Rust 代码的编译器 API。
- 负责解析 `TokenStream` 的 `hello_macro_derive` 函数,
- 和负责转换语法树的 `impl_hello_macro` 函数。
`syn` 代码箱会从一个字符串将 Rust 代码解析为咱们可在其上执行操作的一种数据结构。而 `quote` 代码箱,则会将 `syn` 数据结构,转换回 Rust 代码。这些代码箱令到解析任何一种咱们打算处理的 Rust 代码更为容易:编写出 Rust 代码的完整解析器,并非易事 。
这样做让编写过程宏更加方便。外层函数(这一情形下的 `hello_macro_derive` )中的这种代码,对于咱们所见或创建的几乎所有过程宏代码箱,都将是相同的。而咱们在内层函数(这一情形下的 `impl_hello_macro` )的主体指定的代码,将根据过程宏的用途而有所不同 。
这个 `hello_macro_derive` 函数,将在咱们的库用户,于某个类型上指明 `#[derive(HelloMacro)]` 时被调用。这样做之所以可行,是由于咱们已使用 `proc_macro_derive` 注解了这里的 `hello_macro_derive` 函数,并指定了于咱们的特质名字相符的名字 `HelloMacro` ;而这正是多数程序性宏所遵循的约定。
我们引入了三个新的代码箱:
这个 `hello_macro_derive` 函数首选会将那个 `input` ,从一个 `TokenStream` 转换为咱们随后可以解读并于其上操作的一种数据结构。这正是 `syn` 发挥作用之处。`syn` 中的 `parse` 函数,会取一个 `TokenStream` 并返回一个表示解析出 Rust 代码的 `DeriveInput` 数据结构。下面清单 19-32 给出了咱们对 `struct Pancakes;` 字符串进行解析而得到的 `DeriveInput` 数据结构的有关部分:
- `proc_macro` 、
- [`syn` ](https://crates.io/crates/syn )、
- 和 [`quote` ](https://crates.io/crates/quote )。
`proc_macro` 代码箱是 Rust 默认自带的,因此我们无需添加他到 `Cargo.toml` 中的依赖项。`proc_macro` 代码箱属于编译器的 API, 允许我们在代码中读取和操作 Rust 代码。
`syn` 代码箱能将字符串形式的 Rust 代码,解析为我们可以对其执行操作的数据结构。`quote` 代码箱则将 `syn` 的数据结构重新转换回 Rust 代码。这两个代码箱大大简化了我们处理各类 Rust 代码的解析工作:编写针对 Rust 代码的完整解析器并非易事。
当我们的库用于在某种类型上指定 `#[derive(HelloMacro)]` 时,`hello_macro_derive` 函数就会被调用。这样做之所以可行,是因为我们在这里以 `proc_macro_derive` 注解了 `hello_macro_derive` 函数,并指定了名字 `HelloMacro` ,其与我们的特质名字匹配;这是大多数过程宏遵循的约定。
`hello_macro_derive` 函数首选会将 `input` 从 `TokenStream` 转换为一种数据结构,我们随后对其解析并执行操作。这是 `syn` 发挥作用的地方。`syn` 中的 `parse` 函数会取一个 `TokenStream` 并返回一个 `DeriveInput` 结构体,解析后的 Rust 代码。下面清单 20-41 展示了我们从解析 `struct Pancakes;` 字符串,得到的 `DeriveInput` 结构体的相关部分。
<a name="listing_20-41"></a>
``` rust
DeriveInput {
// --跳过代码--
@@ -261,18 +298,18 @@ DeriveInput {
}
```
* 清单 19-32: 在对 清单 19 -30 中有着该 宏属性的代码进行解析时咱们所 得到的 `DeriveInput` 实例*
**清单 20-41 ** :我们在解析有着 清单 20 -37 中宏属性的代码时, 得到的 `DeriveInput` 实例
这个结构体的字段表明,我们解析的 Rust 代码是个单元值结构体,有着 `Pancakes` 的 `ident` ( *identifier*,即名字)。这个结构体中还有更多字段,用于描述 Rust 的各个方面;请参阅 [ `DeriveInput` 的 `syn` 文档 ](https://docs.rs/syn/1.0/syn/struct.DeriveInput.html ) 了解更多信息。
这个结构体的那些字段显示,咱们所解析的 Rust 是个有着 `Pancakes` 的 `ident` ( 标识符, 意为名字) 的一个单元结构体, a unit struct。此结构体上还有一些用于描述 Rust 各个方面的其他字段;请参阅 [有关 `DeriveInput` 的 `syn` 文档 ](https://docs.rs/syn/1.0/syn/struct.DeriveInput.html ) 了解更多信息 。
很快我们将定义 `impl_hello_macro` 函数,其中我们将构建想要包含的新 Rust 代码。但在我们开始之前,请注意 `derive` 宏的输出也是个 `TokenStream` 。返回的 `TokenStream` 会被添加到我们的代码箱用户编写的代码中,因此当他们编译自己的代码箱时,他们将获得咱们在修改后的 `TokenStream` 中提供的额外功能 。
很快咱们就将实现那个 `impl_hello_macro` 函数,其中咱们将构建出咱们所打算包含的新 Rust 代码。但在咱们实现之前,请注意咱们的派生宏输出,同样是个 `TokenStream` 。这个 返回的 `TokenStream` 会添加到咱们代码箱用户编写的代码,因此当他们编译他们的代码箱时,他们将获得咱们在这个修改的 `TokenStream` 中所提供的额外功能 。
咱们可能已经注意到,我们调用了 `unwrap` ,以在这里的 `syn::parse` 函数调用失败时,引起 `hello_macro_derive` 函数终止运行。我们的过程宏有必要在出现错误时终止运行,因为 `proc_macro_derive` 函数必须 返回 `TokenStream` 而不是 `Result` ,以符合过程宏 API。我们通过使用 `unwrap` 简化了这个示例;在生产代码中,咱们应该通过使用 `panic!` 或 `expect` ,提供有关出错原因的更具体的错误消息 。
咱们或许已经留意到,咱们调用了 `unwrap` ,来在这里的到 `syn::parse` 函数调用失败时,造成那个 `hello_macro_derive` 函数终止运行。由于 `proc_macro_derive` 函数必须返回 `TokenStream` ,而非 `Result` 来顺应程序性宏的 API, 因此咱们的程序性宏就要在出错时终止运行。咱们已通过使用 `unwrap` 简化了这个示例;在生产代码中,咱们应通过运用 `panic!` 或 `expect` ,提供有关那些东西出错的更具体的错误消息 。
现在我们有了将注解后的 Rust 代码从 `TokenStream` 转换为 `DeriveInput` 实例的代码,接下来让我们生成对注解的类型实现 `HelloMacro` 特质的代码,如下清单 20-42 中所示 。
既然咱们有了将经注解的 Rust 代码,从一个 `TokenStream` 转换为一个 `DeriveInput` 实例的代码,那么就要生成在被注解类型上实现这个 `HelloMacro` 特质的代码,如下清单 19-33 中所示。
文件名:`hello_macro_derive/src/lib.rs`
<a name="listing_20-42"></a>
文件名:`projects/hello_macro/hello_macro_derive/src/lib.rs`
``` rust
fn impl_hello_macro ( ast : & syn ::DeriveInput ) -> TokenStream {
@@ -288,35 +325,33 @@ fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
}
```
* 清单 19-33: 是要 解析出 的 Rust 代码, 实现这个 `HelloMacro` 特质*
**清单 20-42 ** :使用 解析后 的 Rust 代码实现 `HelloMacro` 特质
通过 使用 `ast.ident` ,咱们 得到了 一个包含着受注解类型名字(标识符)的 `Ident` 结构体实例。清单 19-32 中的代码结构,显示当咱们在 清单 19 -30 中的代码上 运行这个 `impl_hello_macro` 函数时,咱 们得到的这个 `ident` 就将有着值为有一个 `"Pancakes"` 值 的 `ident` 字段。因此,清单 19-33 中的 `name` 变量,就 将包含一个在被 打印出时,将为 字符串 `"Pancakes"` ,即清单 19 -30 中那个 结构体名字的 `Ident` 结构体 。
我们 使用 `ast.ident` 得到一个 `Ident` 结构体实例,包含被注解类型的名字(标识符)。清单 20-41 中的结构体表明,当我们对 清单 20 -37 中的代码运行 `impl_hello_macro` 函数时,我 们得到的 `ident` 就将有着值为 `"Pancakes"` 的 `ident` 字段。因此,清单 20-42 中的 `name` 变量将包含一个 `Ident` 结构体实例,其 在打印出时将是 字符串 `"Pancakes"` ,即清单 20 -37 中结构体的 名字。
其中的 `quote!` 宏,允许咱们定义出咱们 打算返回的 Rust 代码。编译器会 期望得到不同于这个 `quote!` 宏直接执行结果的东西,因此咱们就 要将其转换为一个 `TokenStream` 。咱们是 通过调用的那个消费这个 中间表示,并返回所需的 `TokenStream` 类型的一个值的 `into` 方法,完成这一点的 。
其中 `quote!` 宏让我们可以定义 打算返回的 Rust 代码。编译器期望的类型与 `quote!` 宏执行的直接结果不同,因此我们需 要将其转换为 `TokenStream` 。我们 通过调用 `into` 方法完成这一操作,其会消费 中间表示形式 ,并返回所需的 `TokenStream` 类型的值 。
`quote!` 宏还提供了一些非常酷的模板机制:咱 们可以敲 入 `#name` ,而 `quote!` 就将使用 变量 `name` 中的值, 替换掉 他。咱们甚至可以与宏工作类似方式,完成 一些重复操作。请参考 [`quote` 代码箱文档 ](https://docs.rs/quote ) 了解完整信息 。
`quote!` 宏还提供了一些非常酷的模板机制:我 们可以输 入 `#name` ,而 `quote!` 将以 变量 `name` 中的值替换他。咱们甚至可以像常规宏那样,执行 一些重复操作。请参考 [`quote` 代码箱的 文档 ](https://docs.rs/quote ) 了解完整的介绍 。
咱们是要这个程序性宏,在 用户注解的类型上, 生成咱 们的 `HelloMacro` 特质实现,而咱 们可通过使用 `#name` 做到这点。这个特质实现,有着一 个名为 `hello_macro` 的函数,其函数体包含了咱们打算 提供的功能:打印 `你好,宏!我的名字叫` 以及随后的那个受 注解类型的名字。
我们希望我们的过程宏针对 用户注解的类型生成我 们的 `HelloMacro` 特质实现,我 们可以 通过使用 `#name` 获取到用户注解的类型。特质实现有 个名为 `hello_macro` 的函数,其函数体包含我们希望 提供的功能:打印 `你好,宏!我的名字叫 ` ,然后是 注解的 类型的名字。
这里用到的那个 `stringify!` 宏,是内建 于 Rust 中的 。他会 取一个 Rust 表达式,比如 `1 + 2` ,并在编译时将这个 表达式转换 为字符串字面值,比如 `"1 + 2"` 。这与 `format!` 或 `println!` 这样的会执行表达式并随后将结果转换为一个 `String` 的宏不同 。由于存在着那个 `#name` 输入,为一个要打印出字面值 的表达式的可能,因此咱们便 使用了 `stringify!` 。使用 `stringify!` 还通过在编译时将 `#name` 转换 为字符串字面值,而节省了 一次内存分配。
这里使用的 `stringify!` 宏内置 于 Rust。他取一个 Rust 表达式,比如 `1 + 2` ,并在编译时转换该 表达式为字符串字面值,比如 `"1 + 2"` 。这与 `format!` 或 `println!` 不同,二者属于会求值表达式然后转换结果为 `String` 的宏。由于存在 `#name` 可能是个要原样打印 的表达式的可能性 ,因此我们 使用 `stringify!` 。使用 `stringify!` 还能 通过在编译时转换 `#name` 为字符串字面值,从 而节省一次内存分配。
此时,在 `hello_macro` 和 `hello_macro_derive` 下的 `cargo build` 都应成功完成。我们来将这两个代码箱连接到清单 20-37 中的代码,看看过程宏的实际操作!使用 `cargo new pancakes` 在咱们的 * projects * 目录下创建一个新的二进制项目。我们需要在 `pancakes` 代码箱的 `Cargo.toml` 中,作为依赖项添加 `hello_macro` 及 `hello_macro_derive` 。当咱们把咱们版本的 `hello_macro` 与 `hello_macro_derive` 发布在 [crates.io ](https://crates.io/ ) 上时,他们就属于常规依赖项;而在没有发布时,咱们可以像下面这样指定他们为 `path` 依赖项:
到这里,在 `hello_macro` 与 `hello_macro_derive` 中,`cargo build` 都应完全成功。让我们来将这两个代码箱,连接到清单 19-30 中的代码,来看看行动中的程序性宏!在咱们的 `projects` 目录下,使用 `cargo new derive_macro_comsumer --vcs none` 创建一个新的二进制项目。咱们需要在这个 `derive_macro_comsumer` 代码箱的 `Cargo.toml` 中,把 `hello_macro` 及 `hello_macro_derive` 添加为依赖项。若咱们把咱们版本的 `hello_macro` 与 `hello_macro_derive` 发布在了 [crates.io ](https://crates.io/ ),那么他们将为一些常规依赖;而在没有发布时,咱们可以像下面这样,将他们指定为 `path` 的依赖:
文件名:`projects/pancakes/Cargo.toml`
``` toml
hello_macro = { path = "../hello_macro" }
hello_macro_derive = { path = "./hello_macro/hello_macro_derive" }
```
请 将清单 19 -30 中的代码, 放入到 `src/main.rs` 中,并运行 `cargo run` : 其 应打印出 `你好,宏!我的名字叫 Pancakes! ` 在这个 `derive_macro_comsumer` 代码箱无需实现那个程序性 宏中的 `HelloMacro` 特质下,该特质的 实现就 已被包含了;正是 `#[derive(HelloMacro)]` 添加了这个 特质实现。
将清单 20 -37 中的代码放入 `src/main.rs` 中,并运行 `cargo run` : 他 应打印出 `你好,宏!我的名字叫 Pancakes! ` 。过程 宏中的 `HelloMacro` 特质实现已被包含,而无需 `pancakes` 代码箱实现他; `#[derive(HelloMacro)]` 添加了该 特质的 实现。
接下来,咱们要 探讨其他类别的程序性宏,与定制派生宏有怎样的 不同。
接下来,我们来 探讨其他类别的过程宏与自定义的 `derive` 宏有何 不同。
## 类属性宏
**Attribute-like macros **
## 类属性的 宏
类属性宏与定制派生宏类似,不过与生成 `derive` 属性的代码不同,他们允许咱们创建出新的属性。他们还更灵活:`derive` 只对结构体和枚举生效;而属性则同时可应用到其他项目,比如函数等。下面就是一个使用类属性宏的示例:比方说咱们在运用某个 web 应用框架时,就有一个对函数加以注解的名为 `route` 的属性: