使用旧的Rust 2015
0.2.0 |
|
---|---|
0.1.0 |
|
0.0.2 |
|
0.0.1 |
|
在 #macro-expansion 中排名 22
每月下载 190 次
17KB
190 行
为什么?
自定义 derive 通常使用 属性 来自定义生成的代码的行为。例如,控制字段在序列化为 JSON 时的名称
#[derive(Serialize, Deserialize)]
struct Person {
#[serde(rename = "firstName")]
first_name: String,
#[serde(rename = "lastName")]
last_name: String,
}
在旧的编译器插件基础设施中,插件提供了标记属性为 "已使用" 的机制,以便编译器知道在运行插件后忽略它们。新的 Macros 1.1 基础设施故意保持最小化,并且不提供此机制。相反,过程宏预期在使用后将属性去除。在过程宏扩展后留下的任何未识别的属性都将转换为错误。
当多个自定义 derive 想要处理相同的属性时,这种方法会导致问题。例如,多个 crate(用于 JSON、Postgres 和 Elasticsearch 代码生成)可能希望对公共重命名属性进行标准化。如果每个自定义 derive 在使用属性后都将其去除,则后续的同结构体的自定义 derive 将看不到它们应该看到的属性。
此 crate 提供了一种在运行其他自定义 derive 后进行后扩展遍历的方式,以移除属性(以及未来可能的其他清理任务)。
如何实现?
假设 #[derive(ElasticType)]
想要利用 Serde 的 rename
属性来处理既可通过 Serde 又可通过 Elasticsearch 序列化的类型
#[derive(Serialize, Deserialize, ElasticType)]
struct Point {
#[serde(rename = "xCoord")]
x: f64,
#[serde(rename = "yCoord")]
y: f64,
}
一个可行但较差的解决方案是让 Serde 的代码生成知道 ElasticType 预期读取相同的属性,因此当 ElasticType 出现在 derive 列表中时不应去除属性。一个理想的解决方案不需要 Serde 的代码生成知道其他自定义 derive 的任何信息。
我们可以通过让 Serialize 和 Deserialize derive 在其他所有自定义 derive 执行后注册一个后扩展遍历来处理属性来解决这个问题。Serde 应将上述代码扩展为
impl Serialize for Point { /* ... */ }
impl Deserialize for Point { /* ... */ }
#[derive(ElasticType)]
#[derive(PostExpansion)] // insert a post-expansion pass after all other derives
#[post_expansion(strip = "serde")] // during post-expansion, strip "serde" attributes
struct Point {
#[serde(rename = "xCoord")]
x: f64,
#[serde(rename = "yCoord")]
y: f64,
}
现在 ElasticType 自定义 derive 可以运行并看到所有正确的属性。
impl Serialize for Point { /* ... */ }
impl Deserialize for Point { /* ... */ }
impl ElasticType for Point { /* ... */ }
#[derive(PostExpansion)]
#[post_expansion(strip = "serde")]
struct Point {
#[serde(rename = "xCoord")]
x: f64,
#[serde(rename = "yCoord")]
y: f64,
}
一旦所有其他 derive 都已扩展,PostExpansion
遍历将去除属性。
impl Serialize for Point { /* ... */ }
impl Deserialize for Point { /* ... */ }
impl ElasticType for Point { /* ... */ }
struct Point {
x: f64,
y: f64,
}
示例之外,还存在一些复杂性。例如,ElasticType 需要注册自己的后扩展传递,以防有人执行 #[derive(ElasticType, Serialize)]
。由于会存在冲突,Serde 和 ElasticType 的后扩展传递不能都命名为 "PostExpansion"。
还有性能考虑。在后扩展传递中删除属性需要额外的往返过程 syn -> tokenstream -> libsyntax -> tokenstream -> syn,如果当前自定义 derive 知道它是最后一个自定义 derive,则可以避免这种情况。
这个 crate 提供了辅助工具,使整个过程 简单、正确且高效。
具体如何实现?
有两个部分。处理属性的 proc macros 需要使用 register_post_expansion!
宏注册后扩展传递。在扩展过程中,它们需要将对应于后扩展传递的自定义 derive 连接起来。
extern crate syn;
#[macro_use]
extern crate post_expansion;
register_post_expansion!(PostExpansion_my_macro);
#[proc_macro_derive(MyMacro)]
pub fn my_macro(input: TokenStream) -> TokenStream {
let source = input.to_string();
let ast = syn::parse_macro_input(&source).unwrap();
let derived_impl = expand_my_macro(&ast);
let stripped = post_expansion::strip_attrs_later(ast, &["my_attr"], "my_macro");
let tokens = quote! {
#stripped
#derived_impl
};
tokens.to_string().parse().unwrap()
}
依赖项
~365–800KB
~18K SLoC