Hèctor M.C.
Hèctor M.C.

Reputation: 115

How to specify conditional code in procedural macros

I am building a procedural macro in Rust that implements, for a given struct, the fmt::Display trait for it. Although the implementation does not change the most of the time, there are some cases the subject struct contains some fields that must be displayed in a specific way.

Inside the macro, I did iterate all input fields to determine if the field is present or not. If it does so, I need to add a specific line at the end of the Display implementation. Otherwise, the line must be ignored.

So, I was wondering, is there any way to add conditional code? Just like I show in the example below:

#[proc_macro_derive(DisplayStruct)]
pub fn display_struct(input: TokenStream) -> TokenStream {
    let DeriveInput { ident, data, .. } = parse_macro_input!(input);
    match data {
        Data::Struct(data_struct) => {
            let mut has_field = false;

            data_struct.fields.iter().for_each(|field| {
                if let Some(ident) = field.ident.as_ref() {
                    has_field |= ident.eq("my_field_name");
                }
            });

            let code = quote! {
                impl std::fmt::Display for #ident {
                    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                        // ...

                        // this IF from here is not part of the Display implementation, but a macro conditional that tells to the macro if the contained line should be added or not.
                        if #has_field {
                            write!(f, "{}", self.my_field_name.to_string())?;
                        }

                        Ok(())
                    }
                }
            };

            return code.into();
        }
        _ => {
            let error: TokenStream = quote!(compile_error!("chonk UwU");).into();
            return error;
        }
    }
}

So the resulting implementation if my_field_name is present should be:

fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    // ...
    write!(f, "{}", self.my_field_name.to_string())?;
    Ok(())
}

Otherwise it must look like this:

fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    // ...
    Ok(())
}

Upvotes: 2

Views: 1157

Answers (1)

Chayim Friedman
Chayim Friedman

Reputation: 70830

Store this line into Option:

let line = has_field.then(|| {
    quote! { write!(f, "{}", self.my_field_name.to_string())?; }
});

Then you can interpolate it into quote!:

let code = quote! {
    impl std::fmt::Display for #ident {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            // ...

            #line

            Ok(())
        }
    }
};

Upvotes: 3

Related Questions