吃得饱系列-写个 Rust 过程宏(derive)

922 阅读2分钟

简介

主要是给一类结构体实现一个没啥 luan 用的 derive 宏,因为开发过程中发觉某一类结构体,一定要实现某个 trait,而且这些 trait 没有啥复杂逻辑,但是必须要实现,等于讲就是要写固定模板代码,既然如此,宏就非常适合做这事。

本来是打算写个规则宏了事,不过写规则宏感觉东一块西一块的,不够高内聚。

创建项目

开始之前,先把项目创建好(项目名字不重要,我只是要在另一个项目中用到它,所以用这个名字。)

cargo new objc-derive

修改 Cargo.toml 文件内容为下面这些

[package]
name = "objc-derive"
version = "0.1.0"
edition = "2021"

[lib]
proc-macro = true

[dependencies]
quote = "1.0"
proc-macro2 = "1.0"
syn = { version = "2", features = ["full"] }

目标使用方法

最终我们要实现一个这样的效果

use objc_derive::Message;

pub mod objc {
  pub trait Message {
    fn send(&self) -> String;
  }
}

#[derive(Message)]
struct Person {
  name: String,
}

impl Person {
  pub fn new(name: &str) -> Person {
    Person { name: name.to_string() }
  }
}

fn main() {
  let person = Person::new("test");
  println!("{}", person.send());
}

其实就是自动给此处的 Person 实现 Message trait

impl Message for Person {
    fn send(&self) -> String {
        self.name.clone()
    }
}

所以,先把 derive 宏的函数体写一下

#[proc_macro_derive(Message)]
pub fn message(i: TokenStream) -> TokenStream {
  TokenStream::new()
}

然后使用之前弄好的两个第三方包来处理输入的 i

use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(Message)]
pub fn message(i: TokenStream) -> TokenStream {
  let syntax_tree = parse_macro_input!(i as DeriveInput);
  TokenStream::new()
}

接下来就是处理 DeriveInput 类型的 syntax_tree 生成新代码

use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(Message)]
pub fn message(i: TokenStream) -> TokenStream {
  let syntax_tree = parse_macro_input!(i as DeriveInput);
  match generate(syntax_tree) {
    Ok(ts) => ts.into(),
    Err(e) => e.to_compile_error().into(),
  }
}
fn generate(syntax_tree: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
  let ident = syntax_tree.ident;
  Ok(quote::quote!(
    use objc::Message;
    impl objc::Message for #ident {
      fn send(&self) -> String {
        self.name.clone()
      }
    }
  ))
}

主要是用到 quote! 这个宏,然后把对应的 trait 给实现了一下,逻辑很简单。


其实是给另一篇文章做铺垫用的,就当水文吧。