Rust那些事之过程宏

2023-02-28 15:47:41 浏览数 (1)

Rust那些事之过程宏

1.过程宏

过程宏是一种扩展Rust编译器和提供可用于扩展该语言的插件的方法。过程宏的工作原理非常简单:取一段称为输入TokenStream的代码,将其转换为抽象语法树(ast),从输入处获得的内容构建一个新的TokenStream(使用syn::parse()方法),并将其作为输出代码注入编译器。

最近在项目中频繁看到各种使用,例如:zombodb中如下使用:

代码语言:javascript复制
#[pg_extern(immutable, parallel_safe)]

在我们平时debug时:

代码语言:javascript复制
#[derive(Debug)]

那么我们如何实现类似Debug的功能呢?

2.derive宏

使用侧:

代码语言:javascript复制
#[derive(StructShow)]
pub struct Point {
    x: f64,
    y: f64
}

我们需要使用proc-macro来实现该功能。

代码语言:javascript复制
cargo new --lib my-macro

在Cargo.toml中设置:

代码语言:javascript复制
[lib]
proc-macro = true

[dependencies]
syn = { version = "1.0.82", features = ["full", "extra-traits"] }
quote = "1.0.10"

一个最基本的例子为:

代码语言:javascript复制
use proc_macro::TokenStream;
use syn::parse;

#[proc_macro_derive(StructShow)]
pub fn whatever_you_want(tokens: TokenStream) -> TokenStream {
    let ast: syn::DeriveInput = syn::parse(tokens).unwrap();
    TokenStream::new()
}

在这个例子中使用了proc_macro,但是对于proc_macro有如下缺点:

  • 需要在proc-macro类型的crate中才能使用
  • 不太好进行测试

因此proc-macro2解决了这些问题,api也基本兼容。

代码语言:javascript复制
#[proc_macro_derive(StructShow)]
pub fn show(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let output = transform_stream(proc_macro2::TokenStream::from(input));
    proc_macro::TokenStream::from(output)
}

在这里将核心操作转入transform_stream函数,在该函数中使用的是proc_macro2,因此可以方便测试在其他crate中直接使用。

随后,内部实现debug功能。

功能非常简单,要实现debug功能,只需要实现Debug trait即可,因此使用quote!将rust语法转位TokenStream返回给编译器即可。

代码语言:javascript复制
fn transform_stream(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
    let ast: ItemStruct = syn::parse2(input).unwrap();
    let struct_type = ast.ident;
    let implemented_show = quote! {
        // 下面就是Display trait的定义了
        // use std::fmt; // 不要这样import,因为std::fmt是全局的,无法做到卫生性(hygiene)
        // 编译器会报错重复import fmt当你多次使用Show之后
        impl std::fmt::Display for #struct_type {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(f, "{} x: {}, y: {}", stringify!(#struct_type), self.x, self.y)
            }
        }
    };
    implemented_show.into()
}

这样,我们在进行打印输出的时候便可以debug了。

代码语言:javascript复制
let p= Point{x: 1.1, y: 2.3};
println!("{}", p); 

今天写的这个例子比较简单,过程宏功能很强大,后续继续研究,期待一起探讨~

本节完~

0 人点赞