Updated 'src/advanced_features/macros.md'.

This commit is contained in:
Hector PENG
2026-04-26 13:11:34 +08:00
parent d539e91761
commit 6aa136dc9b
7 changed files with 104 additions and 92 deletions

View File

@@ -8,5 +8,5 @@ edition = "2021"
proc-macro = true
[dependencies]
syn = "1.0"
syn = "2.0"
quote = "1.0"

View File

@@ -4,12 +4,13 @@ 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)
}

View File

@@ -1,8 +0,0 @@
[package]
name = "hello_macro_derive"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@@ -1,14 +0,0 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View File

@@ -1,9 +1,7 @@
[package]
name = "derive_macro_comsumer"
name = "pancakes"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
edition = "2024"
[dependencies]
hello_macro = { path = "../hello_macro" }

View File

@@ -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` 的属性: