Using a macro to generate struct fields

Hi,

I am coming from C and am used to the macros basically just copy pasting code. I understand that it can cause a lot trouble, but I have some trouble understanding the way it works in Rust. I have used them for simple purposes before, but now have some trouble using them in the following case.

Currently I am trying to use a macro, that sets a struct field by calling a function choose(a, b). It should generate something equvalent to:

MyStruct {
    field_a: choose(self.field_a().clone(), parent.field_a().clone()),
    field_b: choose(self.field_b().clone(), parent.field_b().clone()),
    field_c: choose(self.field_c().clone(), parent.field_c().clone()),
    field_d: choose(self.field_d().clone(), parent.field_d().clone()),
    field_e: choose(self.field_e().clone(), parent.field_e().clone()),
    field_f: choose(self.field_f().clone(), parent.field_f().clone()),
}

My current approach is this:


/// Generates a field using the choose function 
macro_rules! choose_field {
    ($f:expr) => $f: choose(self.$f().clone(), parent.$f().clone())
}

MyStruct {
    choose_field!(field_a),
    choose_field!(field_b),         
    choose_field!(field_c),   
    choose_field!(field_d), 
    choose_field!(field_e),
    choose_field!(field_f),  
}

But the compiler complains about:

($f:expr) => $f: choose(self.$f().clone(), parent.$f().clone())
                       ^ no rules expected this token in macro call

I dont understand why the macro can't call a function?

Macro expansions need to be surrounded with curly braces.

However, there are at least two issues with your code in addition to this immediate syntax error.

First, struct construction doesn't accept macro invocations in field position. MyStruct { choose_field!(field_a) } simply won't work. You'll have to rewrite your macro in such a way that it expands to the complete struct instantiation, rather than individual fields.

Another, higher-level problem is that you are referring to some parent inside the macro. Since declarative macros are hygenic, you can't just "capture" a name from outside the macro. You'll have to explicitly pass the identifier in order to be able to use it.

1 Like

The problem is that Rust macros are only allowed where an expression, a pattern or an item is expected. The field_name: expression syntax if struct analyzation is none of these. You your macro call has to either be something like

MyStruct {
    field_a: choose_field!(parent, field_a),
    field_b: choose_field!(parent, field_b),         
    field_c: choose_field!(parent, field_c),   
    field_d: choose_field!(parent, field_d), 
    field_e: choose_field!(parent, field_e),
    field_f: choose_field!(parent, field_f),  
}

or perhaps something like

choose_fields!(
    parent,
    MyStruct {
        // I don’t know, perhaps want to
        // add some manual fields, too
    },
    field_a,
    field_b,
    field_c,
    field_d,
    field_e,
    field_f,
)

I suspect you don’t have any problems coming up with a macro definition fitting the first version. The second one could use a macro like:

macro_rules! choose_fields {
    (
        $parent:ident,
        $StructName:ident { $($manual_fields:tt)* },
        $($field:ident),+ $(,)?
    ) => {
        $StructName {
        $(
            $field: choose(self.$field().clone(), $parent.$field().clone()),
        )+
            $($manual_fields)*
        }
    }
}

Edit: Fixed the hygiene issues around parent.

1 Like

Thank you for the very quick response - the solution works beautifully! Macros surely are powerful in Rust.