How can I macro to implement `Debug` automatically

How can I make the following macro MyDebug

mod Mod1 {
    #[MyDebug]
    struct MyStruct;
}

The trait MyDebug is accutually doing this:

mod Mod1 {
    struct MyStruct;

    impl std::fmt::Debug for MyStruct {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            let path = get_current_mod_path();
            write!(f, "{}::MyStruct", path)
        }
    }
}

if it is hard to get the current type path, how can I make the alternative macro like following:

mod Mod1 {
    #[MyDebug("Mod1")]
    struct MyStruct;
}

You want to write a procedural derive macro, and make use of the std::module_path macro.

As an aside, MyDebug is not a trait in this code. MyDebug is an attribute macro, but you shouldn't use an attribute macro for this, you should use a derive macro.

3 Likes

Thanks very much, the macro std::module_path is useful.
I realized the attribute macro MyDebug, it works fine for now. This is the code:

#![allow(unused_imports)]
use proc_macro::{self, TokenStream as ts1};
use quote::{quote, quote_spanned};
use syn::{parse_macro_input, DeriveInput, Generics, ImplItem, Data, Fields, spanned::Spanned, Index};
use proc_macro2::TokenStream as ts2;
use synstructure::{AddBounds, Structure};

#[proc_macro_attribute]
pub fn my_debug(_: ts1, input: ts1) -> ts1 {
    let ast: DeriveInput = syn::parse(input).unwrap();
    let name = ast.ident.clone();
    let name_string = name.to_string();
    let b = get_fields_name(&ast.data);
    let output = quote! {
        #ast
        impl std::fmt::Debug for #name {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                f.debug_struct(&format!("{}::{}", module_path!(), #name_string))
                #b
                .finish()
            }
        }
    };
    proc_macro::TokenStream::from(output)

fn get_fields_name(data: &Data) -> ts2 {
    match *data {
        Data::Struct(ref data) => {
            match data.fields {
                Fields::Named(ref fields) => {
                    let recurse = fields.named.iter().map(|f| {
                        let name = &f.ident;
                        let name_string = name.as_ref().map_or_else(String::default, |v| v.to_string());
                        quote! { field(#name_string, &self.#name) }
                    });
                    quote! {
                        #(.#recurse)*
                    }
                }
                Fields::Unnamed(ref fields) => {
                    let recurse = fields.unnamed.iter().enumerate().map(|(i, _f)| {
                        let index = Index::from(i);
                        quote! { &self.#index }
                    });
                    quote! {
                        #(#recurse,)*
                    }
                }
                Fields::Unit => {
                    todo!()
                }
            }
        }
        Data::Enum(_) | Data::Union(_) => unimplemented!(),
    }
}

Just to re-emphasise this: you should not be using an attribute macro for this.

Attribute macros are for cases where you need to modify or completely replace a definition with something else. What you're doing is deriving an implementation of Debug, which is what derive macros are for.

What you've done works, but sets the wrong expectation for anyone else reading the code. It's like clear variable and function naming: not actually required but makes your code much easier to understand.

1 Like

Thanks for your correctness, I've changed to this:

#[proc_macro_derive(PathDebug)]
pub fn path_debug(input: ts1) -> ts1 {
    let ast: DeriveInput = syn::parse(input).unwrap();
    let name = ast.ident.clone();
    let name_string = name.to_string();
    let b = get_fields_name(&ast.data);
    let output = quote! {
        impl std::fmt::Debug for #name {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                f.debug_struct(&format!("{}::{}", module_path!(), #name_string))
                #b
                .finish()
            }
        }
    };
    proc_macro::TokenStream::from(output)
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.