FAQ
什么是 proc-macro crate
proc-macro crate 指 Cargo.toml
中,其 crate type 被定义成如下的 crate:
[lib]
proc-macro = true
它只能导出过程宏。
正常的函数、类型、模块、macro_rules!
等内容都不能导出,但可以仅在其内部定义和使用。
什么是过程宏
- 从形式上看:是带着特定属性的公有函数,其输入为一个或两个 TokenStream,输出是一个 TokenStream
- 从功能上看:是 AST 到 AST 的函数,即从编译器获取和返还 AST
- 与声明宏的关系:是声明宏的拓展,而非声明宏的替代品
类别 | 函数属性 | 公有函数名 | 函数签名 |
---|---|---|---|
函数式 | #[proc_macro] | 函数名即宏名 | (TokenStream) -> TokenStream |
derive 式 | #[proc_macro_derive(Name)] 或者#[proc_macro_derive(Name, attributes(attr))] | 任意,因为宏名是 Name | (TokenStream) -> TokenStream |
属性式 | #[proc_macro_attribute] | 函数名即宏名 | (TokenStream, TokenStream) -> TokenStream |
过程宏的优缺点
优点:
- 过程宏利用了 Rust 强大的静态类型系统的优势,从而以结构化的方式操作语法树
- 函数式过程宏可以实现与声明宏相同的功能
- derive 式过程宏和属性式过程宏对使用者更加方便
缺点:
- 学习成本高:作为过程宏的编写者,你需要对 Rust 非常熟悉(对于过程宏的使用者,学习如何使用过程宏并不难)
- 增加了编译成本
- 相比编写声明宏,过程宏的代码量更多(当然,意味着过程宏的功能更丰富)
derive 宏与属性宏的区别
derive 宏所生成代码是附加性质的,通常生成某类型的 impl
代码,尤其是生成某 trait impl
的代码。
而属性宏更加通用和自由,生成的代码可以是附加或者替换性质的。
编写过程宏所涉及的 crate
proc_macro
这是内置的库。过程宏与之交互的主要类型是 proc_macro::TokenStream
,只能在 proc-macro crate 中使用。
proc_macro2
它对 proc_macro
进行了包装,因此这两个库的所定义类型大多可以相互转化。
更重要的是,proc_macro2
被设计在 proc-macro crate 之外使用,从而在
lib/bin 中可以使用类似于过程宏的一些功能(比如在非
proc-macro crate 中对过程宏进行打印、调试、测试)。
quote
它利用 quote! { #(#value),* }
插值方式,把输入的内容变成 proc_macro2::TokenStream
类型1。
此外,它提供 format_ident!
来方便生成 proc_macro2::Ident
。
它可以使用 from-into
转化成 proc_macro::TokenStream
(过程宏的输出)。
syn
syn
基于 proc_macro2
和 quote
,用于解析 proc_macro::TokenStream
(过程宏的输入)、 Rust
语法或者自定义语法到其内部所定义的语法树节点类型,以及构建和操作节点类型。
最终通过 quote::ToToken
trait 把 syn
节点类型转化成 quote!
可插值的内容,从而返回给编译器。
Span
的作用
过程宏使用
Span
类型来把每个标记和源码的位置、卫生性信息联系起来。为了让编译器生成的错误在正确的地方显示出来,过程宏负责正确传播和操作这些Span
。src:proc-macro-workshop/seq/tests/08-ident-span.rs
即这些 Span
代表标记的源码位置,主要目的是在出现错误时,把错误信息定位到错误源头。
你可以浏览声明宏与不同 Span 的过程宏的错误信息:案例#assert_sync。
当然,它最终是代表位置,因此你可以用它解析其他信息:比如 syn 不提供普通的 //
注释解析,但你可以利用位置手动去解析它们,见
syn: Non-doc comments。
使用同一个 proc-crate 中的多个过程宏
// sorted crate: lib.rs
#[proc_macro_attribute]
pub fn sorted(arg: TokenStream, input: TokenStream) -> TokenStream { ... }
#[proc_macro_attribute]
pub fn check(arg: TokenStream, input: TokenStream) -> TokenStream { ... }
// 方法一:引入再使用属性
use sorted::sorted;
use sorted::check;
#[sorted]
#[check]
// 方法二:直接使用属性
#[sorted::sorted]
#[sorted::check]
proc-macro-workshop
proc-macro-workshop 是什么?
它是 dtolnay 提供的一份过程宏实战教程。你也可以把它看做带你深入了解编写过程宏的 rustlings。
建议:
- 按照作者介绍的顺序编写,因为这个顺序是由易到难的;
- 千万不要忽视 README 和代码中的所有注释;
- 千万不要忽视 syn 和 quote 的文档及其文档样例代码。
你可以参考我的解答(未参考别的解答,完全由我独立编写): https://github.com/zjp-CN/proc-macro-workshop
其他参考资料: