Rust 编写 derive 宏
source link: https://junhaideng.github.io/2022/09/03/rust/macro/derive/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Rust 编写 derive 宏
宏可以帮助我们减少重复代码的编写,在 Rust 中有两种宏定义,
其中,声明宏只是简单的 token
替换,我们无法知道代码结构中的其他信息,过程宏可以获取更加详细的数据,比如我们可以获取结构体中字段的名称,类型等等。
下面介绍如何编写一个简单的 derive 过程宏。
首先对于过程宏来说,不能和引用的 crate
放置在同一个 crate
中,需要单独放置在一个 crate
中,同时我们需要在 Cargo.toml
中配置
[lib]
proc-macro = true
对于 derive
宏而言,即为输入为 TokenStream
,输出也是 TokenStream
的函数,且输出的内容会 append
到代码中,而不会覆盖输入的内容
fn proc_marco(input: TokenStream) -> TokenStream;
我们使用 syn
对输入进行解析,获取其中的 token
信息,使用 quote
构造输出
[dependencies]
quote = "1"
syn = "1.0"
使用 proc_macro_derive
来标注这个函数是一个 derive
宏,同时也可以指定 attributes
用于指定可以在宏范围中使用的 attr
,可以指定多个使用逗号进行分割。
#[proc_macro_derive(PrintField, attributes(field, typ))]
pub fn print_field(input: TokenStream) -> TokenStream;
如果要使用该宏,使用方式如下
#[derive(PrintField)]
struct Server {
#[field="hi"]
host: String,
port: u16
}
derive
宏的具体编写步骤主要分成以下三步
- 使用
syn
提供的方法解析输入
#[proc_macro_derive(PrintField, attributes(field, typ))]
pub fn print_field(input: TokenStream) -> TokenStream {
let DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput);
//....
}
- 从输入中获取到需要的信息,进行保存
#[proc_macro_derive(PrintField, attributes(field, typ))]
pub fn print_field(input: TokenStream) -> TokenStream {
let DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput);
let mut field_names = vec![];
if let syn::Data::Struct(s) = data {
if let syn::Fields::Named(f) = s.fields {
// 获取所有的字段名字,最后进行打印
for field in f.named.iter() {
field_names.push(
field
.ident
.as_ref()
.map(|ident| ident.to_string())
.unwrap_or_default(),
);
}
}
}
println!("{:?}", field_names);
//....
}
- 通过
quote
构造输出
#[proc_macro_derive(PrintField, attributes(field, typ))]
pub fn print_field(input: TokenStream) -> TokenStream {
let DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput);
let mut field_names = vec![];
if let syn::Data::Struct(s) = data {
if let syn::Fields::Named(f) = s.fields {
println!("{:?}", f.to_token_stream().to_string());
for field in f.named.iter() {
field_names.push(
field
.ident
.as_ref()
.map(|ident| ident.to_string())
.unwrap_or_default(),
);
}
}
}
println!("{:?}", field_names);
quote!(
impl #ident {
pub fn hello_world(&self) {
println!("Hello World")
}
}
).into()
}
函数中的 Ident
类型变量,使用 #variable_name
的方式在 quote
中进行引用,不能直接使用字符串,可以通过下面的方式进行创建
use proc_macro2::Span;
use syn::Ident;
Ident::new("fn_name", Span::call_site());
如果需要进行循环迭代,可以使用 #()*
的方式表示
// fn_name 可以理解为 Vec<Ident> 类型
impl #ident {
#(
pub fn #fn_name() {
println!("call {}", #fn_name.to_string())
}
) *
}
因为宏是在编译的过程中进行处理的,所以即使我们宏中代码实现的不高效,不影响运行时性能。编写好的宏,可以使用 cargo expand
命令进行展开,如果提示没找到该命令,使用 cargo install cargo-expand
进行安装。
简单写了一个 derive
宏自动为结构体生成 setter
和 getter
方法,仓库:construct,可以学习参考。
生活杂笔,学习杂记,偶尔随便写写东西。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK