Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 900
lovesh Avatar asked Apr 10 '26 21:04

lovesh


1 Answers

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.

like image 55
Alice Ryhl Avatar answered Apr 12 '26 17:04

Alice Ryhl



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!