Skip to content

RFC: Remove the implicit features associated with dependencies #1286

Closed
@sfackler

Description

@sfackler

Background

Features in a Cargo crate setup can currently be created in two ways. Features can be manually added:

[features]
foo = []
bar = ["a/b", "c"]

Manually added features can depend on other features. bar above depends on feature c from its own crate, and feature b from crate a.

In addition, all dependencies have an implicit associated feature with the same name. However, these features cannot depend on other features.

The Problem

The current setup misses some important use cases. For example, rust-postgres has a uuid feature which adds support for the Postgres UUID type by linking to the uuid crate and generating trait implementations for the uuid::Uuid type. A separate crate, rust-postgres-array adds support for array types. Here, we would like to have the same setup - a uuid feature that adds support for the Postgres UUID[] type by linking to the uuid crate and adding the appropriate trait implementations. The uuid feature of rust-postgres-array needs to depend on functionality provided by the uuid feature of rust-postgres, but this isn't currently possible since uuid is a feature implicitly created by the uuid dependency.

There exists a kind of workaround here, where you define a feature with a different name that depends on uuid:

[features]
uuid_support = ["uuid", "postgres/uuid"]

That's a bit unfortunate, though. Having the feature name match the crate name avoids a bit of verbosity and makes it explicit that it's using that crate specifically (for example, support for the Postgres JSON type is provided via rustc-serialize::Json, and naming the feature rustc-serialize makes this point more clear than naming it json would, as well as keeping room for support via some other crate as well in the future).

I was initially going to change Cargo to add support for an optional dependency depending on features of other dependencies like so:

[dependencies]
postgres = "0.6"

[dependencies.uuid]
optional = true
version = "0.1"
features = ["postgres/uuid"]

After some more thought, I came to the conclusion that this was the wrong way to go about it, and deeper changes to Cargo's feature API would solve both this issue and some other issues:

Backwards compatibility hazards

The fact that all dependencies are also features poses backwards compatibility hazards for a crate author when making changes that would not otherwise be user facing. Imagine Alice makes a crate foo:

[package]
name = "foo"
version = "0.1.0"
authors = []

[dependencies]
bar = "0.1"

And Bob makes a crate baz that depends on foo:

[package]
name = "baz"
version = "0.0.1"
authors = []

[dependencies.foo]
version = "0.1"
features = ["bar"]

Note that he's enabled the bar feature on the foo crate. This is basically a no-op (it defines the feature = "bar" cfg when compiling foo, but let's assume that foo doesn't use that anywhere).

Now imagine Alice does some internal refactoring that allows her to remove the dependency on bar. She publishes version 0.1.1, but suddenly a bug report is filed on foo. The new release broke Bob's crate, since the bar feature no longer exists! Bob should never have enabled that "feature" in the first place, but that doesn't make the situation any better for the people that depend on the baz crate.

So why not forbid the use of features named after non-optional dependencies?

Features matching the name of a non-optional dependency are another important use case! rust-postgres currently depends on time for internal use that doesn't touch the public API. It also implements some traits for time::Timespec to enable support for the Postgres TIMESTAMP type. However, I want to make the TIMESTAMP support opt-in via a feature to enable me to drop the dependency on time if future work on rust-postgres removes its internal usage. If the user facing part (the trait implementations) is behind a feature, I can make the time dependency optional without breaking backwards compatibility.

Proposal

As discussed above, the current feature implementation is both too restrictive to enable some uses and too eager to enable dependency flexibility. Both of these problems boil down to Cargo's intertwining of features and dependencies. It seems like the solution here is to remove that interconnectedness.

Dependencies, mandatory or optional, will no longer automatically have associated features. Features can be defined with two forms. The short form matches visually with current feature syntax, though the meaning will be adjusted:

[features]
bar = ["a", "b/c"]

[dependencies.a]
optional = true

[dependencies.b]
optional = true

This defines a feature bar. While in current Cargo, bar would also activate the a and b/c features, it would now activate the optional dependency on crate a, as well as crate b, activating crate b's feature c. Note that the behavior here is actually identical between current Cargo and Cargo after the proposed changes. It will make a difference in a case like this:

[features]
bar = ["a"]
a = []

In current Cargo, this is valid, and activating feature bar causes feature a to be activated as well. In this proposal, this would be an error, since a is a feature and not a dependency.

If a feature needs to activate another feature, a more verbose method is allowed:

[features]
a = []

[features.b]
features = ["a"]
dependencies = ["foo"]

[dependencies.foo]
optional = true

Having this extended form could also be useful in the future to allow the addition of things like descriptions which could be displayed on crates.io:

[features.foo]
description = "Adds support for the foo thingy"

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-featuresArea: features — conditional compilation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions