Create a struct from macro_rules

Macros must expand to a complete syntax element (e.g. item, expression etc). A list of fields is not a complete anything.

The way to do this is with a recursive macro that builds up partial results by passing them to itself, and then generates the output all at once. This is often called a muncher because it methodically munches through its input.

Your macro, converted to a muncher, would look something like this:

macro_rules! new_struct {
    // input is empty: time to output
    (@munch () -> {$(#[$attr:meta])* struct $name:ident $(($id:ident: $ty:ty))*}) => {
        $(#[$attr])* struct $name { $($id: $ty),* }
    };
    
    // branch off to generate an inner struct
    (@munch ($id:ident: struct $name:ident {$($inner:tt)*} $($next:tt)*) -> {$(#[$attr:meta])* struct $($output:tt)*}) => {
        new_struct!(@munch ($($inner)*) -> {$(#[$attr])* struct $name});
        new_struct!(@munch ($($next)*) -> {$(#[$attr])* struct $($output)* ($id: $name)});
    };
    
    // throw on the last field
    (@munch ($id:ident: $ty:ty) -> {$($output:tt)*}) => {
        new_struct!(@munch () -> {$($output)* ($id: $ty)});
    };
    
    // throw on another field (not the last one)
    (@munch ($id:ident: $ty:ty, $($next:tt)*) -> {$($output:tt)*}) => {
        new_struct!(@munch ($($next)*) -> {$($output)* ($id: $ty)});
    };
    
    // entry point (this is where a macro call starts)
    ($(#[$attr:meta])* struct $name:ident { $($input:tt)*} ) => {
        new_struct!(@munch ($($input)*) -> {$(#[$attr])* struct $name});
        //                 ^^^^^^^^^^^^    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
        //                     input       output
    }
}

new_struct! {
    #[derive(Debug)]
    struct Foo {
        foo: i32,
        bar: struct Bar {
            bar: i32,
            foobar: i64
        }
    }
}

fn main() {
    println!("{:#?}", Foo { foo: 1, bar: Bar { bar: 2, foobar: 3 } });
}

I took some liberties with your syntax because a few things didn't make sense: like where did you plan to get field names or type names for the inner structs?

The general strategy is to transform the input, with its nested structs, into a flat structure that looks like #[$attr]... struct $name { $id: $ty, ...}. But this is kept in the $($output:tt)* argument until the macro is ready to output the whole thing. You can put #![feature(trace_macros)] trace_macros!(true); at the top of the file to see this happening.

I'm happy to add more comments to my code if needed. By the way, I highly recommend reading Daniel Keep's The Little Book of Rust Macros, it explains lots of tricks like this.

2 Likes