前言#
昨天我们熟悉了过程宏的三种类型,今天我们来接触第三方的包,这些包可以大大的简化我们对token stream的处理。
第三方包(Third-Party Crates)#
注意,接下来要介绍的都是第三方的crate,都是需要手动去添加依赖的。
它们可以使过程宏编写变得简单,但代价自然就是编译的时间变长。
proc-macro2#
proc-macro2#
貌似和proc_macro是兄弟俩,但实际上这货是第三方的,同父异母的兄弟
实际上proc-macro2是基于proc_macro封装的一层,用于服务两方面:
- 把类似过程宏(
proc-macro-like)的功能带入其它上下文,比如build.rs或者main.rs。 - 使过程宏支持单元测试
由于proc_macro只能在proc_macro类型的crate中使用,所以想在非过程宏的代码中单元测试它们或者使用它们是不可能的。
而proc-macro2可以让我们在非过程宏的代码中单元测试/使用。
所以建议是基于proc-macro2编写代码,而不是proc-macro。简单地说就是proc-macro有的proc-macro2都有,proc-macro没有的,proc-macro2也有。这也就是为什么后面我们的代码输出的token是: proc-macro2::TokenStream,不过一般我们是使用TokenStream2,重命名下的proc-macro2::TokenStream。
另外如果有需要拿到proc-macro提供的token stream而不是封装之后的,可以通过.into()的方法。
quote#
quote#
这货直往外暴露一个宏:quote!。这个宏允许我们通过写代码的方式简单的创建token stream,另外它也可以让我们从编写的语法中插入或者修改token。
可以通过使用#local语法插入 Interpolation,#local指向一个存在于本地的作用域。另外,你也可以使用使用 #(#local)* 来对实现了 ToTokens 的类型的迭代器进行插值,其工作原理类似于声明宏的反复,因为它们允许在反复中使用分隔符和额外的标记
来看个简单的例子:
let name = /* some identifier */;
let exprs = /* an iterator over expressions tokenstreams */;
let expanded = quote! {
impl SomeTrait for #name { // #name interpolates the name local from above
fn some_function(&self) -> usize {
#( #exprs )* // #name interpolates exprs by iterating the iterator
}
}
};可以看到几乎和平时写rust代码没什么差别。这个库写过过程宏的应该都清楚,基本上都会用到,非常有用,因为原生处理的方式是处理AST token,所以还得熟悉抽象语法树的语法你才能去操作,而抽象语法树的学习成本还是挺高的。
注意:这个包是基于proc_macro2的,所以quote!宏返回的是proc-macro2::TokenStream。
syn#
syn#
和quote类似,这个包我们也是经常使用。它一般是和quote功能配套的,它尅解析一个一段rust代码变成一段抽象语法树token。即syn将源代码转换成抽象语法树,quote将抽象语法树转换成源代码。
它使得解析proc-macro输入变得异常简单,因为proc-macro自己本身不暴露解析抽象语法树的功能,而是把解析的抽象语法树提供给开发者作为输入参数。
另外这货是支持按需引入的,有需要用到的功能在Cargo.toml里配置下即可。
所以这货提供给我们啥能力呢?
- 自然是最重要的,可以定义和解析标准(
standard)rust语法节点(前提是整个包引入,Cargo.toml里配置feature = "full")。DeriveInput类型封装了所有传递给派生宏的信息(feature需要添加derive)。而parse_macro_input!宏需要feature添加parsing和proc-macro两个。 - 如果你还有额外的骚操作,即你的代码不是标准的
rust语法,那么它也有一个泛型解析API(parsing API)提供给你,主要以Parsetrait差不多(需要添加parsing这个feature,默认支持)。 - 它提供的类型都会保持位置信息和范围(
spans),所以在过程宏中报错可以上报出错的位置。
这个crate也是基于proc_macro2的。
卫生和Spans#
由于这一章没啥内容,所以给它合并到一起去了,另外技术Techniques这一大章节内容也是TODO,所以这篇讲完就结束了。。。
这一章我们将简单的说一下过程宏的卫生以及编码的类型:Span。
每一个在 TokenStream中的token都有一个关联的Span保留它的额外信息。
一个span,正如文档对它的描述,是一个源代码的范围,以及宏拓展的信息(A region of source code, along with macro expansion information)
它指向原来代码所在的区域(对于错误诊断来说是非常重要的),同时保持这个位置的卫生类型。这个卫生主要和标识符相关,因为它允许或者禁止标识符引用定义在外部上下文的东西或者被外部上下文的东西引用。
它有三种卫生类型(都可以在Span类型的构造器看到):
definition site(不稳定):表示span定义在宏里面。这种类型的标识符不可以引用外部的东西或者被外部引用。那叫一个卫生mixed site:表示宏定义处或者调用处的span,卫生和声明宏一样,具体取决于标识符的类型。call site:表示调用处span。标识符表现得就像是直接在调用处编写的一样,也就是说,它们可以自由地使用调用之外定义的内容,也可以从外部引用它们。这就是所谓的“不卫生” (unhygienic)。
术语#
如果你觉得翻译有问题,一些关键名词丢失或者有问题,可以去这里提 issue
Function-like macro#
像 macro 这样的函数描述了一个语法扩展,可以通过表单标识符调用!(...).之所以这样称呼它,是因为它类似于函数调用。
Syntax Extension#
Rust 的机制macro_rules!和过程宏是建立在此基础上的。
总结#
今天我们主要了解了可以使过程宏开发变得简单的三个crate。当然,如果你想写的宏非常小非常简单,那就没必要引入,因为它们会使编译时间变长。
然后我们还了解到了过程宏的卫生情况。
后续我会关注教程的更新,如果TODO的内容更新了,我这里也会同步。
