-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Remove the implicit features associated with dependencies #1286
Comments
cc @alexcrichton @wycats this is what I mentioned on IRC earlier today. Thoughts? |
@sfackler I'm glad you decided to flesh out the feature system rather than go down the path of a separate optional dependency system. I originally designed it this way because I think that optional dependencies are just a degenerate form of features. I perhaps overzealously tried to make the simple optional dependency case ergonomic, but at the cost of making things features that shouldn't have been. I think I buy these arguments, and imagine that the impact would be relatively low because features are probably used relatively little at the moment. @sfackler do you agree with my assessment of the impact? |
Yep, I think that's probably the case. We could also build in a bit of a deprecation period for the old style of features without too much pain I think. |
This all sounds like a great idea to me, thanks for writing this up @sfackler! |
Cool, I'll start on the implementation. One thing to clarify before I do: the short form of the feature syntax is mildly inconsistent because it contains dependencies from this crate, and features from other crates. That is, these features are equivalent: [features]
a = ["a", "b/c"]
[features.a]
dependencies = ["a"]
features = ["b/c"] It works this way because that seems to be what you want most of the time, i.e. it's relatively uncommon for one feature to depend on another in the same crate. Does that seem reasonable? |
As @alexcrichton mentioned on IRC, |
See the associated issue for more details. Closes rust-lang#1286.
@sfackler I agree with @alexcrichton |
Feature groups aren't that uncommon. Feature documentation would be great. I want to be able to communicate what features require, in particular two aspects: (1) does this feature require nightly or stable rust (2) Is this feature a stable part of the crate's api or not. |
Another issue of mixing features and dependencies, which I'm not sure is covered by the redesign. Say you have a feature F and a dependency D that also has a feature F. I want to be able to encode the following cases:
The problem is that the current implementation has no way to write option number 2 and 4. If you write |
cc @djc , we were talking about this during the impl days at rustfest |
So the proposal we came up with is that features and dependencies will live in different namespaces. The idea we had is that the dependency activation of dependencies by features will have to be explicit, with syntax something like this: [features]
foo = ["bar", "crate:baz", "baz/spock"] (Since the slash notation has no application for features, the part before the slash can safely be interpreted as a dependency; also note that In our plan, this would be turned on by a configuration boolean in the I'm still wondering if we can have a shortcut by which an optional dependency would result in implicitly adding a feature of the same name as long as a feature of that name does not exist. Because for the simple case of a feature that simply switches on a single dependency seems like annoying boilerplate: [features]
foo = ["crate:foo"] Not sure if we have good reason to disable that shortcut? Remember, as soon as you explicitly define the I am currently working on a bunch of refactoring to clarify the code in the resolver, in a way that should make it possible to abstract over the differences between the old and new models, I hope I can post an initial draft in the next few days. |
I wish I could do: [features.foo.dependencies]
foo = { ... }
bar = { ... }
# ... skipping the indirection of the separately-declared optional dependency. That would be a fine replacement for concision lost if we get rid of the shortcut @djc mentions. Even better, allow features boolean logic like https://github.com/tbu-/rust-rfcs/blob/master/text/1361-cargo-cfg-dependencies.md if a dependency is needed by multiple features. |
@djc it's an interesting idea yeah of maybe not requiring an explicit opt-in here, being able to "shadow" the name of an optional dependency with the name of a feature sounds dangerously plausible to me, so long as we require that the feature doing the shadowing actually does enable the optional dependency! |
I like that idea. e.g. An "openssl" feature must enable the "openssl" dependency and MAY also enable other dependencies. Consider the case where the crate |
@briansmith yeah, that's a great use case to fix as well! I think @djc is working on implementing a fix for this issue, so we should hopefully have this fixed in the near future! |
Right, so: if a feature |
Introduce Requirements struct to clarify code `cargo::core::resolver::build_requirements()` previously, in this order: * Defined local variables to track state * Called a function to mutate the local variables * Defined the aforementioned function * Returned two out of three local variables as a tuple This PR changes the code so that this is a recast as a struct (appropriately called `Requirements`), which is mutated in a more fine-grained way by its methods and acts as the return type for `build_requirements()`. To me, this seems a lot easier to understand. This work was done in the context of #1286, but I figured it was easier to start landing some of the refactoring to avoid bitrot and get early (well, earlier) feedback.
Split resolver module to make it more manageable The `core::resolver` module is currently the largest in Cargo, at some 2000 lines. Here's an attempt at splitting it into more reasonable parts and reordering the code in it for better comprehensibility. Splitting is done in three major steps: * Move the `Resolve` type and its dependencies into a separate module (~220 lines) * Move utility data types into a separate module (~400 lines) * Move the `Context` type and its dependencies into a separate module (~400 lines) This halves the size of the root module, which is then reordered to make it more readable. (This is a yak-shaving expedition of sorts, in preparation for #1286.)
Split resolver module to make it more manageable The `core::resolver` module is currently the largest in Cargo, at some 2000 lines. Here's an attempt at splitting it into more reasonable parts and reordering the code in it for better comprehensibility. Splitting is done in three major steps: * Move the `Resolve` type and its dependencies into a separate module (~220 lines) * Move utility data types into a separate module (~400 lines) * Move the `Context` type and its dependencies into a separate module (~400 lines) This halves the size of the root module, which is then reordered to make it more readable. (This is a yak-shaving expedition of sorts, in preparation for #1286.)
Introduce FeatureValue type to represent features table values This is the next step towards #1286 (after #5258). The goal here is to have a central place in the code where feature strings are interpreted as (a) a feature, (b) a dependency or (c) a dependency/feature combo, and anchor that interpretation in the type system as an enum. I've spent quite a bit of effort avoiding extra string allocations, complicating the code a bit; notice in particular the use of `Cow<str>` in `FeatureValue` variants, and the slight workaround in `Context::resolve_features()` and `build_requirements()`. I hope this is all okay. cc @Eh2406
This is the tracking issue for stabilizing this feature: |
It took me a while to figure out how this proposal actually solves the problem it's trying to address. Here's what I gather that the final TOML for the (fomat in proposal in OP) [dependencies]
postgres = "0.6"
uuid = { version = "0.1", optional = true }
# (now that implicit features are gone, you can explicitly define a "uuid" feature)
[features.uuid]
features = ["postgres/uuid"]
dependencies = ["uuid"] (fomat in more recent discussion; I suppose this is what was implemented?) [dependencies]
postgres = "0.6"
uuid = { version = "0.1", optional = true }
# (now that implicit features are gone, you can explicitly define a "uuid" feature)
[features]
uuid = ["crate:uuid", "postgres/uuid"] |
You're right on the money about what was implemented. Is there something I should add to the namespaced-features documentation in the Cargo book that would make this more accessible? |
Exemplary use cases (mainly TOML snippets) of things not possible without the feature, like:
|
[C-SERDE](https://rust-lang.github.io/api-guidelines/interoperability.html#c-serde) However, crate `serde` is still compiled by cargo even if we compile lumi without enabling feature `serde`. This is similar to [#1286](rust-lang/cargo#1286). Let us wait until `namespaced-features` is stable.
Background
Features in a Cargo crate setup can currently be created in two ways. Features can be manually added:
Manually added features can depend on other features.
bar
above depends on featurec
from its own crate, and featureb
from cratea
.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 auuid
feature which adds support for the PostgresUUID
type by linking to theuuid
crate and generating trait implementations for theuuid::Uuid
type. A separate crate,rust-postgres-array
adds support for array types. Here, we would like to have the same setup - auuid
feature that adds support for the PostgresUUID[]
type by linking to theuuid
crate and adding the appropriate trait implementations. Theuuid
feature ofrust-postgres-array
needs to depend on functionality provided by theuuid
feature ofrust-postgres
, but this isn't currently possible sinceuuid
is a feature implicitly created by theuuid
dependency.There exists a kind of workaround here, where you define a feature with a different name that depends on
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 viarustc-serialize::Json
, and naming the featurerustc-serialize
makes this point more clear than naming itjson
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:
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
:And Bob makes a crate
baz
that depends onfoo
:Note that he's enabled the
bar
feature on thefoo
crate. This is basically a no-op (it defines thefeature = "bar"
cfg when compilingfoo
, but let's assume thatfoo
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 onfoo
. The new release broke Bob's crate, since thebar
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 thebaz
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 ontime
for internal use that doesn't touch the public API. It also implements some traits fortime::Timespec
to enable support for the PostgresTIMESTAMP
type. However, I want to make theTIMESTAMP
support opt-in via a feature to enable me to drop the dependency ontime
if future work onrust-postgres
removes its internal usage. If the user facing part (the trait implementations) is behind a feature, I can make thetime
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:
This defines a feature
bar
. While in current Cargo,bar
would also activate thea
andb/c
features, it would now activate the optional dependency on cratea
, as well as crateb
, activating crateb
's featurec
. 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:In current Cargo, this is valid, and activating feature
bar
causes featurea
to be activated as well. In this proposal, this would be an error, sincea
is a feature and not a dependency.If a feature needs to activate another feature, a more verbose method is allowed:
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:
The text was updated successfully, but these errors were encountered: