Reputation: 171
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
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