Propagating features to consumers

I have a library with an enum that looks somewhat like this:

enum Style {
    Base,
    LineNumber,
    Severity(Level),
    #[cfg(feature = "code-styling")]
    Code(CodeStyle),
    #[doc(hidden)]
    NonExhaustive,
}

And a trait somewhat like this:

trait Stylesheet {
    fn apply(style: Style, to: &mut dyn WriteColor) -> io::Result<()>;
}

I'd like to have downstream users provide library implementations of Stylesheet to be used by other binaries, with the end binary selecting whether they want to include the CodeStyle variant, without the middle library having to also expose a code-styling feature.

What's the best way to let these middle libraries conditionally include code if a feature is enabled in my library? Note that the way that this would likely be used is something like

impl Stylesheet for MyStyle {
    fn apply(style: Style, to: &mut dyn WriteColor) -> io::Result<()> {
        match style {
            #[cfg(has_code_style!())]
            Code(style) => match style {
                _ => unimplemented!(),
            }
            _ => unimplemented!(),
        }
    }
}

To do this well is not supported out of the box, if we take into account that anything else that also depends on your library could have enabled this crate feature.

You'd need the information that the feature is enabled to flow from the dependency to all crates that are compiled against it, and cargo doesn't support that.

What looks tempting is for the middle library to use a configuration test in a build script to know by trial compilation if the feature is enabled or not — but even this is not well supported. It moves the dependency to be a build dependency, and build dependencies are not supposed to share enabled features with regular dependencies. It might so happen that they do right now, but they shouldn't. So I'd avoid this as well.

I'd think that redesigning the enum is the best way to solve this, and have the middle libraries always handle this variant in some way?

2 Likes