// Emit an implementation of std::fmt::Debug for a basic struct with named
// fields and no generic type parameters.
//
// Note that there is no enforced relationship between the name of a derive
// macro and the trait that it implements. Here the macro is named CustomDebug
// but the trait impls it generates are for Debug. As a convention, typically
// derive macros implement a trait with the same name as a macro.
//
//
// Resources:
//
// - The Debug trait:
// https://doc.rust-lang.org/std/fmt/trait.Debug.html
//
// - The DebugStruct helper for formatting structs correctly:
// https://doc.rust-lang.org/std/fmt/struct.DebugStruct.html
use derive_debug::CustomDebug;
#[derive(CustomDebug)]
pub struct Field {
name: &'static str,
bitmask: u8,
}
fn main() {
let f = Field {
name: "F",
bitmask: 0b00011100,
};
let debug = format!("{:?}", f);
assert!(debug.starts_with(r#"Field { name: "F","#));
}
參考連結
這裡主要就是去實現Debug
,其中主要的型別當然還是fields
。
pub(crate) type FieldsType = syn::punctuated::Punctuated<syn::Field, syn::Token!(,)>;
pub(crate) fn parse_fields(ast: &syn::DeriveInput) -> syn::Result<&FieldsType> {
if let syn::Data::Struct(syn::DataStruct {
fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }),
..
}) = ast.data
{
return syn::Result::Ok(named);
}
syn::Result::Err(syn::Error::new_spanned(ast, "parse fields error"))
}
我只能說,和結構相關的話,fields
實在是太過頻繁,縱使不太理解,死記硬背也行。
pub(super) fn solution(
fields: &crate::common::FieldsType,
origin_ident: &syn::Ident,
) -> syn::Result<proc_macro2::TokenStream> {
let field_stream_vec: Vec<_> = fields
.iter()
.map(|f| {
let ident = &f.ident.as_ref();
let ident_string = ident.unwrap().to_string();
quote::quote! {
.field(#ident_string, &self.#ident)
}
})
.collect();
let origin_ident_string = origin_ident.to_string();
syn::Result::Ok(quote::quote! {
impl std::fmt::Debug for #origin_ident {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct(#origin_ident_string)
#(
#field_stream_vec
)*
.finish()
}
}
})
}
這裡不得不提的一點是,我們在quote::quote!
中,傳入的資料要明確的區分型別。
這一點很重要,因為模板裡面涉及到字面量和識別符號。
這個問題在debug
這道題中時刻需要注意,因為ident
和string
使用方式不一樣。
當然,如果你想”{#ident}”.to_string()或許也算是降低理解難度的一種方法。
// lib.rs
mod common;
mod solution2;
fn solution1(ast: &syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let origin_ident = &ast.ident;
let fields = crate::common::parse_fields(&ast)?;
// soluton2
let token_stream = solution2::solution(fields, origin_ident)?;
syn::Result::Ok(token_stream)
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結