derive 宏是怎么从 struct 定义生成 impl 的?泛型怎么处理?错误怎么指到用户代码的位置?展开本身要花多少时间?这篇真实写一个 derive 宏,把每一步都跑出来。
derive 宏的实现(derive_fields/src/lib.rs):
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields};
#[proc_macro_derive(FieldNames)]
pub fn field_names(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let (impl_g, ty_g, where_c) = ast.generics.split_for_impl();
let fields = match &ast.data {
Data::Struct(s) => match &s.fields {
Fields::Named(f) => f.named.iter().map(|f| f.ident.as_ref().unwrap().to_string()).collect::<Vec<_>>(),
_ => return syn::Error::new_spanned(&ast.ident, "FieldNames 只支持有命名字段的 struct").to_compile_error().into(),
},
_ => return syn::Error::new_spanned(&ast.ident, "FieldNames 只能用于 struct").to_compile_error().into(),
};
let n = fields.len();
quote! {
impl #impl_g #name #ty_g #where_c {
pub const FIELD_COUNT: usize = #n;
pub fn field_names() -> [&'static str; #n] { [#(#fields),*] }
}
}.into()
}
cargo build(含下载编译 syn/quote/proc-macro2)用时 1.6 s,退出码 0
运行 app:
User: 3 个字段 ["id", "name", "active"]
Wrapper<i32>: 2 个字段 ["inner", "tag"]过程宏运行的阶段见过程宏的执行阶段。syn 2.x、quote 1.x、proc-macro2 1.x 从 crates.io 下载(需要联网)。
impl User {
pub const FIELD_COUNT: usize = 3usize;
pub fn field_names() -> [&'static str; 3usize] {
["id", "name", "active"]
}
}
impl<T: Clone> Wrapper<T> where T: Default {
pub const FIELD_COUNT: usize = 2usize;
pub fn field_names() -> [&'static str; 2usize] { ["inner", "tag"] }
}标准库 derive(Clone、PartialEq 等)的展开见derive 自动生成 Trait 实现。
error: FieldNames 只能用于 struct
--> src/main.rs:3:28
|
3 | #[derive(FieldNames)] enum Color { Red, Green }
| ^^^^^
error: FieldNames 只支持有命名字段的 struct
--> src/main.rs:4:30
|
4 | #[derive(FieldNames)] struct Pair(i32, i32);
| ^^^^
error: could not compile `app` (bin "app") due to 2 previous errors无 derive:2000 个 struct,cargo build(只重编 app)最快 0.28 s 有 derive:2000 个 struct,cargo build(只重编 app)最快 0.53 s
编译时间是本机测的小样本,受缓存和机器影响。