Search code examples
rustrust-cargo

How can my crate check the selected features of a dependency?


I'm looking for a way to provide special functionality based on a dependency's feature selection.

There is a library crate-a with features feature-a and feature-b. I would like to crate a new library crate-b which depends on crate-a.

Binary crate-c depends on both crate-a and crate-b and specifies features for crate-a.

I would like to provide a different implementation of crate-b's function greet based on the feature set selected for crate-a.

I've tried this approach which doesn't work:

// at crate-b/src/lib.rs

#[cfg(not(feature = "crate-a/feature-a"))]
pub fn greet() {
    println!("General impl");
}

#[cfg(feature = "crate-a/feature-a")]
pub fn greet() {
    println!("Feature-A impl");
}

Is there any way to check crate-a's features from crate-b?

I control both crate-a and crate-b. Even an approach where something should be changed in crate-a works for me.


Solution

  • The only way I can think of that would achieve this at compile time would be to conditionally define macros in crate-a depending on the features enabled. For example, a macro for specifically when feature-a is specified would look like this:

    // in crate-a/src/lib.rs
    #[cfg(feature = "feature-a")]
    #[doc(hidden)]
    #[macro_export]
    macro_rules! __cfg_feature_a {
        ( $( $tok:tt )* ) => { $( $tok )* }
    }
    
    #[cfg(not(feature = "feature-a"))]
    #[doc(hidden)]
    #[macro_export]
    macro_rules! __cfg_feature_a {
        ( $( $tok:tt )* ) => {}
    }
    

    These macros are usable by other crates, but hidden from the public API of crate-a via #[doc(hidden)], and they expand to either the same tokens they were given or an empty body depending on the feature flags. You can then use them in crate-b like so:

    // in crate-b/src/lib.rs
    pub fn unconditional_fn() {}
    
    crate_a::__cfg_feature_a! {
        pub fn cfg_feature_a_fn() {}
    }
    

    This is obviously a pretty hacky solution, and requires a lot of boilerplate defining macros for every combination of feature flags you use, but should work for compile-time conditional compilation based on the features of crate-a.

    Edit: for reference, serde used a similar approach with its serde_if_integer128 macro until version 1.0.187.