(NB: None of the code here has been tested— it may need adjustments before being acceptable)
An alternative solution to your original problem would be to use a single generic struct
:
struct MyGenericStruct<B> {
a: usize,
b: B
}
mod a {
type MyStruct = super::MyGenericStruct<usize>;
}
mod b {
// The b field will exist, but () has zero size; this shouldn’t
// take any more memory than just a `usize.
type MyStruct = super::MyGenericStruct<()>;
}
This can’t always be made to work, however, so it’s also useful to know what a macro solution looks like. If the extra field always has the same name, you can do something like this:
macro_rules! my_macro {
($($my_b_type: ty)?) => {
struct MyStruct {
a: usize,
$(b: $my_b_field)?
}
};
}
The key realization here is that you can include more text in the expansion of a repetition than just the variable being repeated. You can also include multiple variables in the same repetition:
macro_rules! my_macro {
($($field_name:ident : $field_ty:ty)?) => {
struct MyStruct {
a: usize,
$($field_name: $field_ty)?
}
};
}
You can also do more complex things, like calling a macro from within a macro (though this has some subtle limits):
macro_rules! my_macro {
(@ty b) => { usize };
($($extra_field:tt),*) => {
struct MyStruct {
a: usize,
$($extra_field: $crate::my_macro!(@ty $extra_field),)*
}
};
}
For more insight into advanced macros, you can look at The Little Book of Rust Macros and the fourth Rust Koan. They’re both a few years old at this point so some details have changed, but the general concepts are sill sound.