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.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.