How to control conditional compilation via code?

Background

I have two similar structs:

mod a {
    struct MyStruct {
        a: usize,
        b: usize,
    }
}
mod b {
    struct MyStruct {
        a: usize,
    }
}

And I want to use declarative macro to generate those structs in modules, such as:

macro_rules! my_macro {
    () => {
        struct MyStruct {
            a: usize,
            // Not know how to handle conditional compilation
            b: usize,
        }
    };
}

#[cfg] approach, not work

I was thinking about #[cfg] may help, just add #[cfg(my_struct_need_b)] above the b: usize part in the macro rules, like:

macro_rules! my_macro {
    () => {
        struct MyStruct {
            a: usize,
            #[cfg(my_struct_need_b)]
            b: usize,
        }
    };
}

But how do I specify my_struct_need_b feature in module a without module b? In C, we can use #define to define a feature, and use #ifdef and #ifndef to control condition compilation, but Rust doesn't provide a functionality similar to #define, and I found in Rust Reference:

It is not possible to set a configuration option from within the source code of the crate being compiled.

So I think #[cfg] may not work.

Optional macro parameter, not work too

Another way is by optional macro parameter:

macro_rules! my_macro {
    ($($my_b_field: /* no type can work */)?) => {
        struct MyStruct {
            a: usize,
            $($my_b_field)?
        }
    };
}

And use

mod a {
    my_macro!(b: usize);
}
mod b {
    my_macro!();
}

But it has two problems:

  • The type of $my_b_field can't be determined. No type can work.
  • The struct without b field may need to be defined via this macro in multiple modules, it is unwise to write some duplicated long code (field b is an example, may be a very long fragment) in multiple places.

Problem

How to achieve conditional compilation in macros, and control its behavior 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.