Create a struct from macro_rules

Hi all,

It is possible to generate a struct from a macro_rules macro, where the fields are supplied by the tokentree input of the macro? I made the following simplified example:

        macro_rules! new_struct {
        {@inner $id:ident: $ty:ty } => { $id: $ty };
        {@inner $id:ident: $ty:ty, $($next:tt)* } => { $id: $ty, new_struct!{ @inner $($next)* } };
        {@inner $id:ident: $ty:ty {$($inner:tt)*} $($next:tt)* } => { $id: $ty, new_struct!{ @inner     $($inner)* } new_strict!{ @inner $($next)* } };
        {$($inner:tt)* } => {
            struct Foo {
                new_struct!{@inner $($inner)* }
            }
        };
    }
    
    new_struct! { foo: i32 { bar: i32, foobar: i64 } }
    
    //Expected expansion:
    /*
     struct Foo
     {
         foo: i32,
         bar: i32,
         foobar: i64
     }
    */
    
    fn main() {
        println!("Hello, world!");
    }

I seems that the macro inside the struct is not expanded.

I also tried adding generated attributes to a struct and generate an inner struct with a #[derive]. I didn't got the attributes connected to the struct.

Regards,

mredlek

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

Thanks for your response. With this information I succeeded in completing the macro.

My objection was to flatten the result struct. So instead of directly outputting the struct, I took the information up to the top level and made the full struct there.

I also find the documentation you suggested very helpfull. It's a pity I didn't recognize that documentation before.