syn与quote的简单使用——实现debug

发布于:2025-09-01 ⋅ 阅读:(22) ⋅ 点赞:(0)

目录

前言

正文

参考

获取结构体的名字

获取字段

获取结构体字段的名字

最后的拼接

最终代码如下

测试一下


前言

如下代码

#[derive(Debug)]
struct Student{
    name: String,
    id:i32,
    options: Option<String>
}

fn main() {
    let s=Student{
        name:"asdas".to_string(),
        id:1,
        options:Some("test".to_string())
    };
    println!("{:?}", s);
}

打印结果如下

Student { name: "asdas", id: 1, options: Some("test") }

自定义一个Mydebug这个派生宏实现这个过程。

正文

参考

这其实很简单,本质上就是实现Debug这个trait,使用代码实现这个trait,因此,如下

impl Debug for Student {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Student")
            .field("name", &self.name)
            .field("id", &self.id)
            .field("options", &self.options)
            .finish()
    }
}

实现Debug这个trait,需要实现fmt这个方法,在方法中对各个字段进行处理。

所以用宏来操作,两个关键

  1. 结构体的名字
  2. 结构体字段的名字

获取这两个就可以了。行。

获取结构体的名字

定义派生宏

#[proc_macro_derive(MyDebug)]
pub fn my_debug(input: TokenStream) -> TokenStream {


}

转换为派生宏能处理的语法解析树,获取名字

    let input=parse_macro_input!(input as DeriveInput);
    let name=&input.ident;

获取字段

  1. 判断是否是结构体
  2. 判断是否是命名结构体
  3. 返回字段

代码如下

    let fields=match input.data{
        Data::Struct(data)=>{
            match data.fields{
                Fields::Named(fields)=>fields.named,
                _=>panic!("不是命名结构体")
            }
        },
        _=>panic!("不是结构体")
    };

获取结构体字段的名字

fields的类型是Punctuated<Field, Token![,]>,迭代获取其中的名字

    let field_name=fields.iter().map(|field| field.ident.as_ref().unwrap());

最后的拼接

    quote! {
        impl std::fmt::Debug for #name {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                f.debug_struct(stringify!(#name))
                #(
                  .field(stringify!(#field_name), &self.#field_name)
                )*
                .finish()
            }
        }
    }.into()

#(...)*:对迭代器中的每个元素重复生成代码片段

很简单。

最终代码如下

use proc_macro::TokenStream;
use quote::quote;
use syn::DeriveInput;
use syn::{parse_macro_input, Data, Fields};
#[proc_macro_derive(MyDebug)]
pub fn my_debug(input: TokenStream) -> TokenStream {
    let input=parse_macro_input!(input as DeriveInput);
    let name=&input.ident;
    println!("结构体的名字:{}",name.to_string());
    let fields=match input.data{
        Data::Struct(data)=>{
            match data.fields{
                Fields::Named(fields)=>fields.named,
                _=>panic!("不是命名结构体")
            }
        },
        _=>panic!("不是结构体")
    };
    let field_name=fields.iter().map(|field| field.ident.as_ref().unwrap());
    quote! {
        impl std::fmt::Debug for #name {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                f.debug_struct(stringify!(#name))
                #(
                  .field(stringify!(#field_name), &self.#field_name)
                )*
                .finish()
            }
        }
    }.into()

}

测试一下

use debug_my::MyDebug;
#[derive(MyDebug)]
struct Student {
    name: String,
    id: i32,
    options: Option<String>,
}
fn main() {
    let s = Student {
        name: "asdas".to_string(),
        id: 1,
        options: Some("test".to_string()),
    };
    println!("{:?}", s);
}

结果如下

Student { name: "asdas", id: 1, options: Some("test") }

没问题。


网站公告

今日签到

点亮在社区的每一天
去签到