元编程的另一种形式,即编译器插件,可以在编译时运行任意Rust代码。 这个特性是本书中唯一没有达到Rust稳定版本的特性(也许永远不会以这种形式出现),但它仍然被广泛使用,并且应该涵盖一个重要的差异化特征。
本章预计会有一定程度的厚度和不确定性; 编译器插件是一个具有挑战性的功能,它们的Rust实现仍然非常不稳定。 这些概念应该相当稳定,但即使在发布日期后一年,实施细节也可能会有所不同。
在本章中,我们将介绍以下主题:
最小的编译器插件
Cargo整合
代码生成
Aster(用于创建抽象语法树的库)
Linter插件
宏1.1
宏2.0
编译器插件的基础知识
我们在上一章中看到的宏在编译时将语法转换为另一种语法。 这是一个很好的工具,因为它允许多种形式的编译时操作,同时提供干净和稳定的API。 但是,我们不能通过文本操作来完成大量令人满意的编译时间。 这里有一些:
- 额外的代码验证检查(lints)。
- 编译时验证:数据库架构检查,主机名验证。
- 根据环境生成代码。 例如,从实时数据库表创建数据模型,从环境填充数据结构,通过在编译时计算昂贵的东西来优化运行时性能,等等。
虽然前一章中的宏示例具有创建宏的特殊语法,而模式匹配是中心结构,但编译器插件只是我们在编译过程中包含的Rust函数。 使它们与众不同的是它们采用预定义的形式将代码块描述为AST并返回任意代码块,再次编码为AST。
以下是广泛使用编译器插件的当前库的列表:
diesel是一个安全,可扩展的数据库对象关系映射器。 它使用编译器插件通过在编译时连接到实际数据库并读取实时模式来生成和验证数据库接口模型代码。 在撰写本书时,Diesel刚刚被移植到使用宏1.1,所以当你读到它时,它可能完全适用于稳定的Rust。
serde是一个通用的序列化/反序列化框架。 它使用编译器插件添加新的派生关键字Serialize和Deserialize,它可以从几十种不同数据格式的结构中生成序列化和反序列化代码。 该框架可以将新格式流畅地添加为单独的库。 此外,Serde将针对宏1.1,这意味着它应该已经完全可用于稳定的Rust。
rust-clippy通过数百个额外的检查扩展了Rust编译器。 这仅适用于夜间Rust,并且预计宏1.1不足以满足这些要求。
编译器插件是一个非常不稳定的功能,因此本章的某些部分可能比其他章节更早过时。 但是,这些概念应该相当稳定,并且应该预期Rust官方的每晚版本应始终包含https://doc.rust lang.org/nightly/book/compiler-plugins.html中的最新详细信息。
让我们首先构建一个非常简单的编译器插件
最小的编译器插件
略
通过Cargo构建编译器插件
略
代码生成作为变通方法
如前所述,编译器插件不是一个稳定的功能,但它们足够有用,以便人们想要使用它们的功能。 有一种解决方法已经在稳定分支中运行:通过名为libsyntex的库生成代码。 它的工作原理是将整个Rust编译器捆绑在一个库中并使用它来实现编译器插件。 这不是编译器插件的替代品,因为它们在夜间Rust中; 为了使代码生成方法起作用,需要对代码库进行许多小的更改。
在实践中,这通过在以.in结尾的模板文件中移动具有编译器插件功能的任何代码模块来工作。 然后在编译步骤之前使用单独的构建脚本(通常称为build.rs)来生成扩展代码扩展的相同模块。 让我们用你好的例子来试试吧。
首先,我们需要将代码树重组为两个 Cargo项目,一个持有插件,另一个使用它。 这是树的外观:
需要稍微更改hello_plugin库:
// compiler-plugin/stable/hello_plugin/src/lib.rs
extern crate syntex;
extern crate syntex_syntax;
use syntex_syntax::tokenstream::TokenTree;
use syntex_syntax::ext::base::{ExtCtxt, DummyResult, MacResult};
use syntex_syntax::ext::quote::rt::Span;
use syntex::Registry;
fn hello<'cx>(_: &'cx mut ExtCtxt, sp: Span, _: &[TokenTree])
-> Box<MacResult + 'cx> {
println!("Hello!");
DummyResult::any(sp)
} p
ub fn register(reg: &mut Registry) {
reg.add_macro("hello", hello);
}
主要是,crates命名不同(syntex_syntax而不仅仅是语法),宏的生命周期现在是<'cx>而不是<'static>(因为使用syntex时静态生命周期不可用)和Registry API call已从register_macro更改为add_macro。 API更改是编译器插件的一般不稳定性的一部分; 当你读这篇文章时,它可能是其他的东西。 syntex有点像黑客,随着宏的稳定,它的使用应该会减少。 这是一个有用的中间步骤,有助于在今天的稳定编译器上使用不稳定的宏。