bluestacks454
bluestacks454

Reputation: 171

Add serde serialize/deserialize within macro_rules! in Rust

I am working on a macro that computes the number of fields on a struct at runtime. I have an implementation whose source code is given below.

use std::rc::Rc;
use std::cell::Cell;

macro_rules! generate {
    ($name:ident {$($field:ident : $t:ty),+}) => {
        struct $name { $($field: $t),+ }
        impl $name {
            fn field_count(&self) -> usize {
                generate!(@count $($field),+)
            }
        }
    };
    (@count $t1:tt, $($t:tt),+) => { 1 + generate!(@count $($t),+) };
    (@count $t:tt) => { 1 };
}

generate! { test1 { num: i32, s: Option<String> }}
generate! { test2 { num: i32, s: String }}

fn main() {
    let s = test1 { num: 0, s: None };
    println!("{}", s.field_count());
    let s = test2 { num: 0, s: String::new() };
    println!("{}", s.field_count());
}

The source code works fine except for the case when the struct is made serde Serializable or Deserializable. I am looking to add a rule inside macro_rules!{} such that it takes a directive like #[derive(Deserialize, Serialize)] within 'generate', such that at compile time the macro allows me to count the number of fields as well as its serializable/deserializable.

Upvotes: 1

Views: 860

Answers (1)

Mac O&#39;Brien
Mac O&#39;Brien

Reputation: 2907

You can capture attributes like #[derive(...)] using the meta fragment specifier. For example, a macro pattern to capture any number of attributes is:

$(#[$attr:meta])*

Which is then used in the output like so:

$(#[$attr])*

Note that the meta specifier doesn't capture the #[] wrapper, so you have to write it explicitly in the pattern.

This can be used to capture attributes on structs and struct fields, in this case transparently passing them through to the macro output. Here's a version of your macro that allows attributes on the struct and its fields (playground link):

use serde::Serialize;

macro_rules! generate {
    (
        // Capture any number of attributes on the struct
        $(#[$attr:meta])*

        struct $name:ident {
            $(
                // Capture any number of attributes on each field
                $(#[$field_attr:meta])*
                $field:ident : $t:ty
            ),+

            $(,)?
        }
    ) => {
        // Emit the struct attributes
        $(#[$attr])*
        struct $name {
            $(
                // Emit the field attributes
                $(#[$field_attr])*
                $field: $t
            ),+
        }

        impl $name {
            fn field_count(&self) -> usize {
                generate!(@count $($field),+)
            }
        }
    };
    (@count $t1:tt, $($t:tt),+) => { 1 + generate!(@count $($t),+) };
    (@count $t:tt) => { 1 };
}

generate! {
    #[derive(Serialize)]
    struct Test1 {
        num: i32,
        #[serde(skip_serializing_if = "Option::is_none")]
        s: Option<String>,
    }
}

fn main() {
    let s = Test1 { num: 0, s: None };
    println!("Number of fields: {}", s.field_count());
    println!("Serialized: {}", serde_json::to_string(&s).unwrap());
}

The output of the program is:

Number of fields: 2
Serialized: {"num":0}

...showing that the skip_serializing_if field attribute was preserved.

Upvotes: 4

Related Questions