Author: Mi Mingheng/Post editor: Zhang Handong

Blog.ideawand.com contributed this article


In the previous article in this series, we practiced the Builder topic for the Proc_Macro_workshop project. It also provides an overview of the proc_Macro_workshop project. If you are reading this series for the first time and are not familiar with the structure of the Proc_Macro_workshop project, you may want to read the previous article first.

All right, without further ado, get a computer ready and start our second challenge task debug

Start by opening the readme.md file for the proc_Macro_workshop project to see what debug does for the project. The ultimate goal of this topic is to implement a derived macro that can output debugging information in the specified format. This macro does the same thing as the Debug derived macro of Rust, except that the macro is more powerful and can specify the output format of each field.

As mentioned earlier, there are three types of process macros in Rust: derived, attribute, and functional. The process macros discussed in this article and last were derived, and the other two styles will be covered in the next three topics. If you are not familiar with procedure macros for derived styles, be sure to read the previous article in this series. This article introduces the debug challenge, in addition to using a lot of the knowledge points used in the last builder project, mainly added to the processing of generics.

The first level

The first level video version: www.bilibili.com/video/BV1vU…

The first step is to set up a framework, configure cargo. Toml, and convert the input TokenStream to syn::DeriveInput. We need to implement a framework that looks like the following structure for error handling:

use proc_macro::TokenStream;
use syn::{self, spanned::Spanned};
use quote::{ToTokens, quote};

#[proc_macro_derive(CustomDebug)]
pub fn derive(input: TokenStream) -> TokenStream {
    letst = syn::parse_macro_input! (inputas syn::DeriveInput);
    match do_expand(&st) {
        Ok(token_stream) => token_stream.into(),
        Err(e) => e.to_compile_error().into(),
    }
}

fn do_expand(st: &syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
    let ret = proc_macro2::TokenStream::new();
    return Ok(ret);
}
Copy the code

Second,

Second, video version: www.bilibili.com/video/BV1Kf…

The second level implements the basic Debug Trait, which is implemented using the STD :: FMT ::DebugStruct structure provided by the Rust library, such as the one below

struct GeekKindergarten {
    blog: String,
    ideawand: i32,
    com: bool,}Copy the code

We implement the Debug Trait by generating code for the following pattern:

impl fmt::Debug for GeekKindergarten {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct("GeekKindergarten")
           .field("blog", &self.blog)
           .field("ideawand", &self.ideawand)
           .field("com", &self.com)
           .finish()
    }
}
Copy the code

So, we can do this in println! The use of {:? } to print the individual fields in the structure.

fn main() {
    let g = GeekKindergarten{blog:"foo".into(), ideawand:123, com:true};
    println!("{:? }", g);
}
Copy the code

We need to read the name of each field of the structure to be processed by the procedure macro, and then generate the above code according to the template. There is no new knowledge, so we can give the code directly:

fn do_expand(st: &syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
    letret = generate_debug_trait(st)? ;return Ok(ret);
}

type StructFields= syn::punctuated::Punctuated<syn::Field,syn::Token! (,) >;fn get_fields_from_derive_input(d: &syn::DeriveInput) -> syn::Result<&StructFields> {
    if let syn::Data::Struct(syn::DataStruct {
        fields: syn::Fields::Named(syn::FieldsNamed { refnamed, .. }),.. }) = d.data{return Ok(named)
    }
    Err(syn::Error::new_spanned(d, "Must define on a Struct, not Enum".to_string()))
}

fn generate_debug_trait(st: &syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
    letfields = get_fields_from_derive_input(st)? ;let struct_name_ident = &st.ident;
    let struct_name_literal = struct_name_ident.to_string();

    let mutfmt_body_stream = proc_macro2::TokenStream::new(); fmt_body_stream.extend(quote! ( fmt.debug_struct(#struct_name_literal)// Note that this is a string, not a SYN ::Ident, and the generated code is represented literally
    ));
    for field in fields.iter(){
        let field_name_idnet = field.ident.as_ref().unwrap();
        letfield_name_literal = field_name_idnet.to_string(); fmt_body_stream.extend(quote! ( .field(#field_name_literal, &self.#field_name_idnet)  This line also pays attention to the difference between literal and ident)); } fmt_body_stream.extend(quote! ( .finish() ));letret_stream = quote! (impl std::fmt::Debug for #struct_name_ident {
            fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
                #fmt_body_stream
            }
        }
    );

    return Ok(ret_stream)
}
Copy the code

The third pass

The third pass video version: www.bilibili.com/video/BV1df…

This level requires that we can specify our own independent formatting style for each field in the structure. That is, we can write lazy attribute annotations like the following inside the structure:

#[derive(CustomDebug)]
struct GeekKindergarten {
    blog: String.#[debug = "0b{:32b}"]    In derived procedure macros, this is an 'lazy property', which was introduced in the previous article
    ideawand: i32,
    com: bool,}Copy the code

In the above code, #[debug= XXX] is the lazy property of our CustomDebug derived macro. The lazy property concept of the derived macro was introduced in the previous article, so we can refer to the lazy property extraction function written in the previous article. One hint is that you can continue to use print to analyze the structure of the property as you write.

  • In the last article, the lazy attribute we dealt with was#[builder(each=xxxx)]When parsed into a syntax tree, this is a two-level nested structure, outsidebuilder(xxxx)To the syntax tree nodeMetaListType structure, while internaleach=xxxxTo the syntax tree nodeNameValueType structure
  • What this article deals with is directly#[debug = xxxx]So it’s actually a little bit easier to deal with than the last problem

After we get from inert properties format template, the next thing to do is to use FMT package provides relevant methods, to format our structure, this part just has nothing to do with the process of the macro development, mainly is the FMT package in the formatting tool usage, the resources in the third test file has also been given, I copy it in the following, You can see for yourself:

  • Doc.rust-lang.org/std/macro.f…

From reading the resources above, we can see that to format the output in the debug_struct tool method, we use format_args! Macro to generate code such as the following:

// It looks like this:
// .field("ideawand", &self.ideawand)
// Now it looks like:
.field("ideawand", &format_args!("0b{:32b}".self.ideawand))
Copy the code

With the above analysis, we can directly give the core code of the third level, the first is the function to extract the lazy attribute of the field:

fn get_custom_format_of_field(field: &syn::Field) -> syn::Result<Option<String> > {for attr in &field.attrs {
        if let Ok(syn::Meta::NameValue(syn::MetaNameValue {
            ref path,
            ref lit,
            ..
        })) = attr.parse_meta()
        {
            if path.is_ident("debug") {
                if let syn::Lit::Str(ref ident_str) =lit {
                    return Ok(Some( ident_str.value() )); }}}}Ok(None)}Copy the code

Then modify the generate_debug_trait() function, which shows only the code snippet of the adjusted core loop body

fn generate_debug_trait(st: &syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
    
    // < unmodified code omitted here >................

    for field in fields.iter(){
        let field_name_idnet = field.ident.as_ref().unwrap();
        let field_name_literal = field_name_idnet.to_string();
        
        // The following lines of code up to the end of the loop body are the parts of the third level that are modified
        let mut format_str = "{:? }".to_string();
        if let Some(format) = get_custom_format_of_field(field)? {
            format_str = format;
        } 
        // No user - defined format is specifiedfmt_body_stream.extend(quote! ( .field(#field_name_literal, &format_args!(#format_str, self.#field_name_idnet))
        ));

    }

    // < unmodified code omitted here >................

}
Copy the code

【 To be continued 】