lovesh
lovesh

Reputation: 5411

DRY for Rust conditional compilation with features

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.

Upvotes: 3

Views: 1293

Answers (1)

Alice Ryhl
Alice Ryhl

Reputation: 4239

You could use the cfg_aliases crate, although it requires adding a build script.

// Cargo.toml
[build-dependencies]
cfg_aliases = "0.1.0"
// build.rs
use cfg_aliases::cfg_aliases;

fn main() {
    // Setup cfg aliases
    cfg_aliases! {
        type_a: { any(feature = "F1", feature = "F2") },
        type_b: { any(feature = "F3", feature = "F4") },
        type_c: { feature = "F5" },
    }
}
#[cfg(type_a)]
fn do_onething_for_type_A(... ) {...}

#[cfg(type_a)]
fn do_another_thing_for_type_A(... ) {

#[cfg(type_b)]
fn do_onething_for_type_B(... ) {

Alternatively you can define macros like Tokio does.

macro_rules! cfg_type_a {
    ($($item:item)*) => {
        $(
            #[cfg(any(feature = "F1", feature = "F2"))]
            $item
        )*
    }
}
cfg_type_a! {
    fn do_onething_for_type_A() {
        ...
    }
}
cfg_type_b! {
    fn do_onething_for_type_B() {
        ...
    }
}

Note that the macro-based approach can cause trouble for any of users of the library using the CLion IDE. When using that IDE, you have to enable

Settings > Languages & Frameworks > Rust > Expand declarative macros: Use experimental engine

to get type completion for things defined behind macros such as above.

Upvotes: 4

Related Questions