Pre-Pre-RFC: weak Cargo feature activation

This is more spitballing than a "Pre-RFC", but just an idea I had given a particular problem.

Problem: inflexible/unintended feature activation

Let's say we have a Cargo.toml that looks like this:

[dependencies]
foo = { version = "1", optional = true, default-features = false }
bar = { version = "2", optional = true, default-features = false }
baz = { version = "3", optional = true, default-features = false }

Now we want to add a std feature, and activate the transitive std feature of those dependencies. We could do this:

[features]
std = ["foo/std", "bar/std", "baz/std"]

...but that means just by activating std (perhaps just because we wanted such a feature in the toplevel crate) we pull in all of the dependencies. This is even worse if we do:

[features]
default = ["std"]
std = ["foo/std", "bar/std", "baz/std"]

...because it means we MUST pull in all dependencies by default, even if we might want to include some but exclude others.

Solution: weak features

What'd be nice is if the std feature could "weakly activate" the corresponding std features of those optional dependencies: instead of automatically pulling them in, it will activate std, but only if the parent crate is pulled in via another feature activating it.

Hypothetical syntax using the + character as a sigil (I don't really care about the concrete syntax or if this is in some way ambiguous, but just to give you an idea of how it could work):

[features]
default = ["std", "foo"]
std = ["foo/std+", "bar/std+", "baz/std+"]

This would:

  • Enable the std and foo features of the toplevel package by default
  • Enable foo/std (i.e. the std feature of `foo)

This would not:

  • Pull in bar and baz as dependencies, unless they were activated in some other way (e.g. --features). But if they were, bar/std and baz/std would be enabled.
6 Likes

See also:

Wow, okay, seems there's some decent precedent here!

Some alternative suggested syntaxes from that thread:

Targetey

[target.'cfg(feature = "std")'.dependencies]
serde = { version = "*", optional = true }

[target.'cfg(not(feature = "std"))'.dependencies]
serde = { version = "*", optional = true, default-features = false }

Maybe

[features]
default = ["std"]
std = ["?create/std"]
serde = ["?create/serde"]

The ? syntax definitely seems more "Rustic" than my suggestion, otherwise it seems like the same idea. Of these two it's what I'd prefer.

1 Like

Long-term, I'd be thrilled to see the cfg syntax used for the fully general case. But this is common enough that I think it makes sense to have a shorthand syntax.

As a minor bikeshed, I personally favor cratename?/feature, because that feels more like "asking the question" in the right place: "cratename?" seems suggestive of "do we have cratename?".