「这是我参与2022首次更文挑战的第 20 天,活动详情查看:2022首次更文挑战」。
过程宏
过程宏是宏的进阶版本。过程宏允许你扩展Rust的现有语法。它接受任意的输入并返回有效的Rust代码。
过程宏是一种接受一个 TokenStream 作为输入并返回另一个 TokenStream 的函数。过程宏对输入的 TokenStream 进行操作,产生一个 output stream。
目前有3种类型的过程宏类型:
- 属性宏
- 派生宏
- 函数宏
我们将在下面详细介绍每种过程宏类型。
属性宏
属性宏可以让你创建一个自定义的属性,将其附加到一个item上,并允许对这个item进行操作。它也允许接受参数。
#[some_attribute_macro(some_argument)]
fn perform_task(){
// some code
}
在上面的代码中,some_attribute_macros 就是一个属性宏。它控制下面的函数 perform_task。
要编写一个属性宏,首先要用 cargo new macro-demo --lib 创建一个lib。项目准备好后,更新 Cargo.toml,即标记该项目将创建过程宏:
# Cargo.toml
[lib]
proc-macro = true
下面开始过程宏的冒险吧!!!
过程宏接受 TokenStream 作为输入并返回另一个 TokenStream。所以在写一个过程宏过程中,我们需要写一个解析器来解析输入参数 TokenStream。Rust社区有一个非常好的crate: syn,用于解析 TokenStream。
# Cargo.toml
[dependencies]
syn = {version="1.0.57",features=["full","fold"]}
quote = "1.0.8"
现在我们可以在 [lib.rs](<http://lib.rs>) 中使用编译器提供的 proc_macro crate 来编写过程宏。在过程宏的crate中不能输出除过程宏以外的其他东西,而且crate中定义的过程宏不能在当前crate中使用。
// lib.rs
extern crate proc_macro;
use proc_macro::{TokenStream};
use quote::{quote};
// using proc_macro_attribute to declare an attribute like procedural macro
#[proc_macro_attribute]
// _metadata is argument provided to macro call and _input is code to which attribute like macro attaches
pub fn my_custom_attribute(_metadata: TokenStream, _input: TokenStream) -> TokenStream {
// returing a simple TokenStream for Struct
TokenStream::from(quote!{struct H{}})
}
为了测试我们添加的宏,我们创建 test 文件夹并在该文件夹中添加 attribute_macro.rs。在这个文件中,我们可以使用我们的过程宏进行测试。
// tests/attribute_macro.rs
use macro_demo::*;
// macro converts struct S to struct H
#[my_custom_attribute]
struct S{}
#[test]
fn test_macro(){
// due to macro we have struct H in scope
let demo=H{};
}
使用 cargo test 运行上述测试。
现在我们已经了解了过程宏的基本知识,下面我们用 syn 来进行一些高级的 TokenStream 操作和解析。
为了学习syn是如何用于解析和操作的,让我们从 syn 的 GitHub repo 中取一个例子。这个例子创建了一个Rust宏,当值发生变化时可以追踪变量。
首先,我们需要确定我们的宏将如何操作它所附加在的代码:
#[trace_vars(a)]
fn do_something(){
let a=9;
a=6;
a=0;
}
上述代码的意思:trace_vars macro 接受一个需要跟踪变量的名称,并在跟踪变量的值即a每次发生变化时注入一条打印语句。