How to conditionally change a small part of a Rust macro?

Viewed 751

I am building a crate which has the feature foo. This crate has a macro bar! that does subtly different things based on whether foo is set.

I could duplicate the entire macro:

#[cfg(feature = "foo")]
macro_rules! bar {
    // Lots of rules...

    ( A ) => {
        B
    }
}

#[cfg(not(feature = "foo"))]
macro_rules! bar {
    // Lots of rules...

    ( A ) => {
        C
    }
}

That's a lot of error-prone duplication. Two approaches that don't work:

  • We can't move the cfg inside the macro, because then it will expand in the crate user's scope, which does not have feature foo appropriately set.

  • We can't use a #[doc(hidden)] #[macro_export] macro_rules! bar_priv_impl__ helper macro and use #[cfg] on the helper macro since Rust 1.30, because users can now request use mycrate::bar; to only import the bar! macro, giving errors about how bar_priv_impl__! is not defined.

Is there any way that's better than full macro duplication? It gets really bad if you have N features you're testing on, as you need 2n duplications.

1 Answers

since Rust 1.30 [...] because users can now [...] only import the bar! macro

Actually, this solution is only possible in Rust 1.30 because of the ability to import macros like normal. Remember that your macro can also have use statements!:

#[macro_export]
macro_rules! bar {
    ($val:expr) => {{
        use $crate::__bar_foo;
        __bar_foo!($val)
    }}
}

#[cfg(feature = "foo")]
#[macro_export]
macro_rules! __bar_foo {
    ($val:expr) => ($val + 1)
}

#[cfg(not(feature = "foo"))]
#[macro_export]
macro_rules! __bar_foo {
    ($val:expr) => ($val - 1)
}

You can also fully-qualify your helper macro invocation:

#[macro_export]
macro_rules! bar {
    ($val:expr) => ($crate::__bar_foo!($val))
}
Related