DRY for Rust conditional compilation with features

Reposting my StackOverflow question.

My library has several features, say F1, F2, F3, F4,.. and only one of them can be active at a time. These features are further classified as types A, B, C, so for example, features F1 and F2 are of type A, F3, F4 are of type B and so on.

I have several occurrences of such code (in the library)

#[cfg(any(feature = "F1", feature = "F2"))]
fn do_onething_for_type_A(... ) {
// repeating same cfg predicate as above
#[cfg(any(feature = "F1", feature = "F2"))]
fn do_another_thing_for_type_A(... ) {
#[cfg(any(feature = "F3", feature = "F4"))]
fn do_onething_for_type_B(... ) {

Is there a way to write the above cfg predicates concisely so that I don't have to mention each feature in the #[cfg(any(.. every time I have that condition? Verbosity is not the only issue. Every time I introduce a new feature, say F5 which is of type, say, A, I have to update the occurrences of line #[cfg(any(feature = "F1", feature = "F2"))] to #[cfg(any(feature = "F1", feature = "F2", feature = "F5"))].

My first thought was to create an attribute based on the feature and then use the attribute as below but seems I can't do that.

#[cfg(any(feature = "F1", feature = "F2"), typeA)]
#[cfg(any(feature = "F3", feature = "F4"), typeB)]

#[typeA]
fn do_onething_for_type_A(... ) {...}

#[typeA]
fn do_another_thing_for_type_A(... ) {

#[typeB]
fn do_onething_for_type_B(... ) {

Declaring a new feature for types A, B, C is my last resort.

Try the cfg_if crate.

@bjorn3 Can you please elaborate?

It allows you to do things like

cfg_if::cfg_if! {
    if #[cfg(unix)] {
        fn foo() { /* unix specific functionality */ }
    } else if #[cfg(target_pointer_width = "32")] {
        fn foo() { /* non-unix, 32-bit functionality */ }
    } else {
        fn foo() { /* fallback implementation */ }
    }
}

or in your case:

cfg_if::cfg_if! {
    if #[cfg(any(feature = "F1", feature = "F2"))] {
       fn do_onething_for_type_A(... ) { ... }
       fn do_another_thing_for_type_A(... ) { ... }
    }
}

You could use the cfg_aliases crate, although it requires adding a build script. Alternatively define macros like Tokio does.

1 Like

I have to use the following verbose code at each usage which I am trying to avoid

cfg_if::cfg_if! {
    if #[cfg(any(feature = "F1", feature = "F2"))] {
.....

Thanks @alice. cfg_aliasses seems to be the thing i was looking for.

Note that your case seems to be "tree shaped". If that's the case, you can have internal features:

# Cargo.toml
[features]
_A = []  # F1 | F2 for something specific
F1 = ["_A"]
F2 = ["_A"]

_B = []
F3 = ["_B"]
F4 = ["_B"]

and then #[cfg(feature = "_A")]. This, on paper, without concrete names, may look worse than actual aliases, bu in practice the pattern you describe is that "_A" is rather something like "timers", "_B" is "filessystem", _etc., which are low-level feature specific "cargo features",
and then you have the higher level "profile-style", "cargo features": F1, F2, etc.

1 Like

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.