「深挖Rust」Rust macro 实例 - 4

329 阅读2分钟

「这是我参与2022首次更文挑战的第 20 天,活动详情查看:2022首次更文挑战」。


过程宏

过程宏是宏的进阶版本。过程宏允许你扩展Rust的现有语法。它接受任意的输入并返回有效的Rust代码。

过程宏是一种接受一个 TokenStream 作为输入并返回另一个 TokenStream 的函数。过程宏对输入的 TokenStream 进行操作,产生一个 output stream

目前有3种类型的过程宏类型:

  1. 属性宏
  2. 派生宏
  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是如何用于解析和操作的,让我们从 synGitHub repo 中取一个例子。这个例子创建了一个Rust宏,当值发生变化时可以追踪变量。

首先,我们需要确定我们的宏将如何操作它所附加在的代码:

#[trace_vars(a)]
fn do_something(){
		let a=9;
	  a=6;
	  a=0;
}

上述代码的意思:trace_vars macro 接受一个需要跟踪变量的名称,并在跟踪变量的值即a每次发生变化时注入一条打印语句。