How to control conditional compilation via code?

(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.