We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Modules, Visibility, Testing and Docs step 16 of 22
Cargo features and the additivity rule
Features are the most misunderstood part of Cargo and the source of the ecosystem’s worst build failures. The resolution algorithm is a small, elegant graph problem; the design constraint that falls out of it is the thing you have to internalise before you design a crate.
pub fn resolve_features(
features: Vec<(String, Vec<String>)>,
optional_deps: Vec<String>,
requested: Vec<String>,
no_default: bool,
) -> Vec<String>
features is the [features] table as (name, entries) pairs. optional_deps
is the list of dependencies declared optional = true. Return the sorted set of
everything that ends up activated.
The four kinds of entry
[features]
default = ["json"]
json = ["dep:serde", "serde?/derive"]
extra = ["serde?/rc"]
fast = ["dep:rayon", "rayon/threads"]
- a plain name — activate that feature of this package.
-
dep:name— activate the optional dependencyname. Notably it does not create a feature calledname. -
pkg/feat— activatepkg‘s featurefeat, and ifpkgis optional, activatepkgtoo. -
pkg?/feat— the weak form. Activatepkg‘s featurefeatonly ifpkgends up activated by some other route. Never pulls the dependency in by itself.
Represent activation results as: the feature name for your own features,
dep:name for an activated optional dependency, and pkg/feat for an activated
dependency feature.
Why the weak form forces a fixpoint
Look at extra = ["serde?/rc"] and json = ["dep:serde", …].
Request ["extra"] alone and nothing happens — serde is not in, so the weak
entry does nothing. Request ["extra", "json"] and serde/rc must end up
activated. But if your algorithm walks the activated set once, in sorted order,
it looks at extra before json, decides serde is not activated, and drops
serde?/rc on the floor.
One pass is not enough. Loop until the activated set stops growing. That is the core of this problem and it is the shape of the real algorithm.
The implicit feature
Declaring a dependency optional = true implicitly creates a feature of the
same name that turns it on — unless you mention it as dep:name somewhere in
the table, which suppresses the implicit feature.
That default is a trap for library authors: the moment you publish with
optional = true and never write dep:, your users can write
features = ["serde"], and you can never remove that feature without a major
version bump. Your choice of dependency has become public API. Writing
dep:serde instead keeps the dependency an implementation detail behind a
feature you named yourself.
The additivity rule
Here is the constraint everything else follows from.
When several packages in a build depend on the same crate, Cargo enables the UNION of all the features they asked for. There is no way to say “and not that one”. So a feature must only ever add behaviour.
The canonical wrong design is a no_std feature. It removes functionality, so
if any package anywhere in the graph enables it, everybody gets it, and a crate
three levels away that needed std fails to compile. The right shape is an
additive std feature that is on by default and can be switched off with
default-features = false.
Unification happens across the whole workspace, which is why cargo test can
end up with more features enabled than cargo build — dev-dependencies join the
union. That is the classic “works locally, fails in CI” (or the reverse), and
the reason is never the machine.
Contract
-
defaultis activated unlessno_default, and only if the table has it. - A requested name that is not a feature is ignored.
- Entries naming a feature that does not exist are ignored.
-
A strong
pkg/featactivatesdep:pkgonly whenpkgis an optional dependency; a non-optional dependency is already there.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.