Cargo: Document that "features should be additive"

Created on 25 Jul 2017  路  6Comments  路  Source: rust-lang/cargo

Although it seems to be well-known, I find it surprising that the requirement "features should be additive" is not documented at all on docs.crates.io. Disrespecting this leads to tricky errors when merging features like chyh1990/yaml-rust#44 and Geal/nom#544 (= #4323).

rust-lang/rfcs#1841 mentioned some documentation changes which should better be added here.

A-documenting-cargo-itself

Most helpful comment

@daboross It takes time for cargo updates to be published on the nightly channel (usually about a week). Until then, the new chapter can be found in the source tree, with the discussion of features being additive starting around https://github.com/rust-lang/cargo/blob/master/src/doc/src/reference/features.md#feature-unification.

All 6 comments

Hey! This is surprising to me as I see a lot of crypto crates that let you choose your backend via mutually exclusive features. Is there any plan to actually enforce this? And what are the issues with mutually exclusive features?

How would that be enforced?

Cargo takes the union of all features that are enabled for a crate throughout the dependency graph. If two crates end up enabling two mutually exclusive features of another crate, then they'll both be enabled when building it. The result of that would depend on the implementation of the crate.

An easy way would be to actually build the crate with --all-features and notice that it fails, but I can see how that would be too much overhead :)

It doesn't seem to be a problem only for mutually exclusive crates.

For example imagine that:

  • you have crate A and B in your workspace
  • hey both call crate C which verifies signatures of some critical payload unless used with feature flag "no_verify"
  • crate A actually needs to skip verification, while crate B doesn't
  • do we end up with both crates skipping verification?

(you can imagine the verification being skipped with if cfg!(feature = "no_verify") which seems like it was created for exactly this kind of usecases)

I wrote a blog post on this topic. Parts of it can probably be adapted for the docs. Full post: https://dev.to/rimutaka/cargo-features-explained-with-examples-194g. The relevant part is copied here. I'm happy to make a PR if this description is acceptable. It probably needs to be much shorter to go into the reference.


Cargo takes the union of all features enabled for a crate throughout the dependency graph. If multiple crates enable mutually exclusive features of another crate, then all those features will be enabled at build time. The result of that would depend on the implementation of the crate and may produce a compiler error if mutually exclusive crates or features are enabled.

An example of this type of dependency would be Crate X that depends on Crates A and Crate B, while both A and B depend on Crate awesome.

       Crate X
      /        \
Crate A        Crate B
      \        /
    Crate awesome

In the following example both go-faster and go-slower features will be enabled in crate awesome. It will be up to that crate to decide which of the two features prevails.

  • Crate awesome:
[features]
"go-faster" = []
"go-slower" = []
  • Crate A: awesome = { version = "1.0", features = ["go-faster"] }
  • Crate B: awesome = { version = "1.0", features = ["go-slower"] }

Consider a more complicated example with three possible configurations for some_core dependency.

  • Crate awesome:
[dependencies]
some_core_default = { package = "some_core", version = "0.1" }
some_core_openssl = { package = "some_core", version = "0.1", default_features = false, features=["openssl"], optional = true }
some_core_rustls = { package = "some_core", version = "0.1", default_features = false, features=["rustls"], optional = true }

[features]
default = ["some_core_default"]
openssl = ["some_core_openssl"]
rustls = ["some_core_rustls"]

The following combination will make crate awesome depend on some_core_rustls because the resulting tree includes default-features = false, features = ["rustls"] which overrides the default:

  • Crate A: awesome = { version = "1.0" }
  • Crate B: awesome = { version = "1.0", default-features = false, features = ["rustls"] }`

Removing default-features = false results in a compilation error because the same some_core dependency is included twice. Once via default and once via rustls:

  • Crate A: awesome = { version = "1.0" }
  • Crate B: awesome = { version = "1.0", features = ["rustls"] }

The following combination will also result in the same compilation error because package some_core is included twice via some_core_openssl and some_core_rustls:

  • Crate A: awesome = { version = "1.0", default-features = false, features = ["openssl"] }
  • Crate B: awesome = { version = "1.0", default-features = false, features = ["rustls"] }

@ehuss Does #8997 resolve this?

I've been reading over the new https://doc.rust-lang.org/nightly/cargo/reference/features.html, but I can't find anything which discourages of forbids non-additive features.

I think ideally this should be explicitly documented in the features documentation. Is there something I'm missing or can we potentially reopen this?

@daboross It takes time for cargo updates to be published on the nightly channel (usually about a week). Until then, the new chapter can be found in the source tree, with the discussion of features being additive starting around https://github.com/rust-lang/cargo/blob/master/src/doc/src/reference/features.md#feature-unification.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

disconsented picture disconsented  路  3Comments

briansmith picture briansmith  路  3Comments

dotnetspec picture dotnetspec  路  3Comments

japaric picture japaric  路  3Comments

alilleybrinker picture alilleybrinker  路  3Comments