How to design conflict features?

For example, my crate supports one of two features, curl or hyper. Both must be defined and cannot be selected at the same time.

Now my solution is:

Cargo.toml:

[features]
default = []
with-curl = ["isahc", "futures-timer"]
with-hyper = ["tokio", "hyper"]

[dependencies]
hyper = { version = "0.13.2", optional = true }
isahc = { version = "0.8.2", default-features = false, features = ["static-curl"], optional = true }
tokio = { version = "0.2.11", features = ["time"], optional = true }

All dependencies are optional, so reduce uselessness dependencies.

src/lib.rs:

#[cfg(not(any(feature = "with-curl", feature = "with-hyper")))]
compile_error!("Either feature `with-curl` or `with-hyper` should be enabled.");

#[cfg(all(feature = "with-curl", feature = "with-hyper"))]
compile_error!("feature `with-curl` or `with-hyper` shouldn't be enabled both.");

Is it a good design?

No, this could lead to broken dependencies if two projects use the same version of your library (say 0.2) but with each with different features. It would be better to pick on as the default and then have a feature to switch to the other. That way everyone's can still build if the feature is selected.

In general, features should be purely additive to be work well with cargo.

3 Likes

Thanks!

The specific problem you have to defend against is when you have a diamond pattern, e.g. top-level application depends (maybe indirectly) on crates A and B which both depend on your crate. If A enables one feature and B enables the other, then you could have an impossible situation which the app-writer (way up the chain of crates) can't resolve. So if possible you should deal with this by making "all features enabled" be something that all users of the crate can be happy with. But if you really can't handle it then you just can't, and that situation would be unresolvable.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.