思路介绍
本章将从整体角度解释和介绍过程宏。
与声明宏不同,过程宏采用 Rust 函数的形式,接受一个(或两个)标记流并输出一个标记流。
过程宏的核心只是一个从 proc-macro
crate type 这种类型的库中所导出的公有函数,因此当编写多个过程宏时,你可以将它们全部放在一个 crate 中。
注意:在使用 Cargo 时,定义一个
proc-macro
crate 的方式是将Cargo.toml
中的lib.proc-macro
键设置为 true,就像这样[lib] proc-macro = true
proc-macro
类型的 crate 会隐式链接到编译器提供的 proc_macro 库,
proc_macro 库包含了开发过程宏所需的所有内容,并且它公开了两个最重要的类型:
TokenStream
:它表示我们所熟知的标记树Span
:它表示源代码的一部分,主要用于错误信息的报告和卫生性,更多信息请阅读 卫生性和 Spans 一章
因为过程宏是存在于 crate 中的函数,所以它们可以像 Rust 项目中的所有其他条目一样使用。
使用过程宏只需要将 proc-macro 类型的 crate 添加到项目的依赖关系图中,并将所需的过程宏引入作用域。
注意:调用过程宏与编译器展开成声明宏是在同一阶段运行,只是过程宏是编译器编译、运行、最后替换或追加的独立的 Rust 程序。
过程宏的类型
过程宏实际上存在三种不同的类型,每种类型的性质都略有不同。1
- 函数式:实现
$name!$input
功能的宏 - 属性式:实现
#[$input]
功能的属性 - derive 式:实现
#[derive($name)]
功能的属性
译者注:你可以参考我总结的表
函数式
#[proc_macro]
pub fn name(input: TokenStream) -> TokenStream {
TokenStream::new()
}
属性式
#[proc_macro_attribute]
pub fn name(attr: TokenStream, input: TokenStream) -> TokenStream {
TokenStream::new()
}
derive 式
#[proc_macro_derive(Name)]
pub fn my_derive(input: TokenStream) -> TokenStream {
TokenStream::new()
}
如上所示,每个函数的基本结构是相同的:一个标记了一个属性的公有函数,这个属性定义了它的过程性宏类型,然后函数返回一个 TokenStream
。
注意,返回类型必须是一个 TokenStream
2。
过程宏也会失败,它们有两种报告错误的方式:
- panic:此时编译器会捕获到,然后把它作为来自于宏调用的错误发出
- 调用
compile_error!
注意:如果过程宏内出现无限循环,编译器会长时间等待(挂起),从而造成使用过程宏的 crate 也编译挂起。
译者注:而且这个 TokenStream
类型必须是 proc_macro
所公开的 TokenStream
,通常使用 quote
库构造这种类型。