๐ŸŽ“ A1Learner
๐Ÿ”

Rust Feature Unification

Feature unification builds one copy of a crate with every requested feature switched on. A crate can therefore gain a feature that another crate requested.

Two requests, one crate

A helper crate asks for red. Your program asks for blue. Both requests land on the same flaglib:

// helper crate
flaglib = { path = "../flaglib", features = ["red"] }

// your program
flaglib = { path = "../flaglib", features = ["blue"] }

The crate sees the union

flaglib is built once, with both switches on. Both callers read the same answer:

println!("app sees: {}", flaglib::flags());
println!("helper sees: {}", usesred::report());

The reporting code, for reference

This is flaglib behind the outputs above. It simply lists the switches it was built with:

pub fn flags() -> String {
    let mut on = Vec::new();
    if cfg!(feature = "red") { on.push("red"); }
    if cfg!(feature = "blue") { on.push("blue"); }
    on.join(" ")
}

The old resolver leaks build features

A build dependency runs on your machine at build time. Under resolver "1", its feature requests leak into the normal build of the same crate. This build dependency asks for red:

[build-dependencies]
flaglib = { path = "../flaglib", features = ["red"] }

The new resolver keeps the sides apart

This program asks for no features at all. Under resolver "2" it prints none, even though the build side asked for red. Under resolver "1" the same program prints red:

fn main() {
    println!("app side: {}", flaglib::flags());
}

Example

// Cargo.toml
// [dependencies]
// usesred = { path = "../usesred" }   (asks flaglib for "red")
// flaglib = { path = "../flaglib", features = ["blue"] }
fn main() {
    println!("app sees: {}", flaglib::flags());
    println!("helper sees: {}", usesred::report());
}

Key points

  • Feature unification: Cargo builds one copy of a crate per build, with the union of every feature anyone requested
  • A feature you did not ask for can still be on.
  • A crate below you in the graph may have switched it on.
  • The resolver is the rulebook for this merging.
  • Edition 2021 and later default to resolver "2".
  • Older workspaces may still use "1"
  • Use resolver = "2" (or "3") in the [workspace] table of the root manifest.
  • It keeps build and normal builds from trading features.
  • Use cargo tree -e features to see the features each crate in your graph was built with

In this example, the helper crate usesred asks flaglib for its red feature. The program asks flaglib for blue. Cargo does not build flaglib twice. It builds one copy with both features on. So the program sees red blue, and the helper sees the very same red blue. Neither side got exactly what it asked for. Both sides got the union.

Note

Unification is why a feature must only ever add.

If a feature could remove code, one crate in the graph could break another crate it has never met.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Rust Playground

Exercise

Your program asks for no features of flaglib. A helper crate asks for its red feature. What does your program see?