The Cargo Features Garden is a collection of examples demonstrating the sometimes surprising behavior of Cargo Features. The intent is to take a clear but critical look at how Cargo Features work under the hood and how the Cargo.toml manifest may contribute to misunderstandings.
The directories in this repository aim to be self-descriptive, and contain Cargo Workspaces or contain a series of directories that contain workspaces. Many of the crates in these repository will not compile and this is as intended. If running cargo build
-- without specifying a --package
-- in any of these workspaces does not yield an error, that is purely by coincidence. Each of these directories contains a README.md that will discuss the contents, the crates intended to be built, and the expected results.
It will be common for directories in a workspace to be named something like 1_lib-a
, 1_lib-b
, 2_exe-a
. In this case, the crates will be named lib-a
, lib-b
, and exe-a
, and the numerical prefix will suggest the crate's level in the implied dependency tree. lib-a
and lib-b
are at the lowest level, so neither depend on any crate in the workspace. exe-a
is at the second level, probably because it depends on one or both of the libraries.
This might also be a suggested reading order. If so, the suggestion is very loose.
A relatively straight-forward demonstration of the possibly surprising nature of feature unification.
An exploration of union'd feature flags focusing on no_std
. (Well, it builds into a discussion of no_std
. Spoilers, I guess.)
An exploration of union'd feature flags focusing on deeply nested, target-specific features.
An exploration of possible methods to encode mutually exclusive features into crates.
Below is a list of active, interrelated issues relating to Cargo Features, brief descriptions of them, and links to salient GitHub issues (mostly in the rust-lang/cargo
Features issue list.
When you include a crate in more than one context -- in both [dev-dependencies]
and [build-dependencies]
, behind [target.'cfg(...)'.dependencies]
expressions, transitively from other dependencies, etc. -- all of the features activated across all instances of the dependency are union'd prior to a build. That means you could write something like,
[dependencies]
some-lib = *
[target.'cfg(false)'.dev-dependencies]
some-lib = { version = "*", features = ["broken-experimental"] }
expecting some-lib
to never have the broken-experimental
feature activated. In reality, no mater what target or profile this crate is built with, the broken-experimental
feature will always be active on some-lib
.
The rust-lang issues and discussions surrounding this behavior can be broken down into three rough categories;
-
Target Specific Features
Additional duplicates w/o meaningful conversation; rust-lang/cargo#3741, rust-lang/cargo#6870, rust-lang/cargo#7292
-
[build-dependencies]
interacting with[dependencies]
- rust-lang/cargo#2589
- rust-lang/cargo#4361
- rust-lang/cargo#4866
- rust-lang/cargo#5237
- rust-lang/cargo#5730 (The comments in this one are important)
There are a couple issues that are more correctly dependency interference issues, but may be correctly handled in the conversation regarding
[build-dependencies]
. -
[dev-dependencies]
interacting with[dependencies]
Activating a Feature that Activates a Feature on an Optional Dependency also Activates the Optional Dependency
This one is somewhat self-explanatory, if you can parse the above title. If you have an optional dependency,
optional-lib = {version = "*", optional = true }
... and a feature that enables a feature on that optional dependency,
some-feature = ["optional-lib/foo"]
... activating that feature also activates the optional dependency. Which means that this,
# Activate the dependency on `optional-lib`, and the feature `optional-lib/foo`.
my-lib = { features = ["optional-lib", "some-feature"] }
... is the same as this,
# Activate `optional-lib/foo` and imply the dependency on `optional-lib`.
my-lib = { features = ["some-feature"] }
Additional duplicates w/o meaningful conversation; rust-lang/cargo#5023, rust-lang/cargo#6658, rust-lang/cargo#7259
When authoring code that needs to be platform-aware, it seems to be a common pattern to homogenize system-level operations by implementing similarly named modules, and use
ing them like,
#[cfg(unix)]
pub use crate::sys::unix as sys;
#[cfg(windows)]
pub use crate::sys::windows as sys;
#[cfg(target_arch = "wasm32")]
pub use crate::sys::wasm as sys;
This can also be a useful pattern for selecting a specific implementation of a more general operation. For example flate2
defaults to using miniz as a compression/decompression library, but can optionally be configured to use zlib, or miniz_oxide. Building flate2
with cargo build --features "zlib rust_backend"
throws no errors, but it's unclear (to me, at least) which backend will be used in that case.
It seems a reasonable ask to for Cargo to provide a mechanism for enforcing mutually exclusive features.
I need to do more research here to dig into the specifics. Generally, Features aren't great at multiple contexts, and Workspaces aren't well polished. The interaction between these two features results in a less than stellar user experience.
- rust-lang/cargo#4463
- rust-lang/cargo#5015
- rust-lang/cargo#5364
- rust-lang/cargo#6458
- rust-lang/cargo#7218
Additional duplicates w/o meaningful conversation; rust-lang/cargo#5251