Skip to content
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

Ability to disable individual default features #3126

Open
ideasman42 opened this issue Sep 27, 2016 · 36 comments
Open

Ability to disable individual default features #3126

ideasman42 opened this issue Sep 27, 2016 · 36 comments
Labels
A-features Area: features — conditional compilation C-feature-request Category: proposal for a feature. Before PR, ping rust-lang/cargo if this is not `Feature accepted` S-needs-rfc Status: Needs an RFC to make progress.

Comments

@ideasman42
Copy link

ideasman42 commented Sep 27, 2016

The current documentation here: http://doc.crates.io/manifest.html states:

With the exception of the default feature, all features are opt-in. To opt out of the default feature, use default-features = false and cherry-pick individual features.

This is quite rigid, since it means you can't make a small adjustment to defaults - where a program may have multiple options which aren't related.

It also means if I use a 3rd party package with modified defaults, after an update I need to check if defaults where added which I might want to enable.


Note, I asked this question on stackoverflow, it was suggested to open an issue here:
http://stackoverflow.com/questions/39734321

@alexcrichton
Copy link
Member

Yeah it's true that default features are a very weighty decision and are quite difficult to reverse. I don't think we can add the ability to force-disable them, however, because how would we know whether a dependency actually needs the feature or not?

@ideasman42
Copy link
Author

@alexcrichton, maybe this is a bigger change then I'd expected, am not very experienced using Cargo.

Could dependencies list features they require?

@alexcrichton
Copy link
Member

Dependencies already do, the default feature is just special where it's turned on by default. If a crate has a feature that may be disabled then in general crates shouldn't move it to a default feature.

@ideasman42
Copy link
Author

In that case wouldn't it be possible to disable a default - having the same behavior as if you explicitly listed all defaults, without the one(s) which have been requested to be disabled?

@alexcrichton
Copy link
Member

Yeah you can disable default features with default-features = false, but Cargo also unions features requested for a crate so if any crate doesn't disable a default feature then it ends up enabled.

@tbu-
Copy link
Contributor

tbu- commented Feb 6, 2017

This is a backward-compatiblity hazard. E.g.: It seems that libcore is getting default features, relatively harmless ones, they change some formatting. Some crate today might opt out of default features and only select the float formatting thing. This means that libcore can never add another default feature that disables existing stuff without breaking that crate.

The better way to handle that would be that each crate has a list of default features that it does not require. This way, default features can be added in the future.

@ideasman42
Copy link
Author

I don't know about libcore but at an application level - it often makes sense to be able to disable dependencies (which are default since they are used in official release builds).

In for you may want to build FFMPEG with patented codecs or not. You may want to build a video editor with FFMPEG or not.
This is typically the case for file formats, codecs, or optional scripting languages (as VIM has with Python, Ruby, Lua... etc)

@tbu-
Copy link
Contributor

tbu- commented Feb 7, 2017

Note that I don't talk about hard-disabling dependency feature, but rather the equivalent of today's default-features = false and then a list of all the other features in the features = [] list. That means if some other dependency pulls in my disabled default feature, I still get it.

@alexcrichton
Copy link
Member

@tbu- the idea seems sound to me at least!

@kornelski
Copy link
Contributor

I'd especially like this to work on command line. --no-default-features is lengthy, and requires user to repeat other defaults. A syntax sugar for it would be very helpful.

Here's a proposal:

Features prefixed with - are removed from the set of default features, i.e.:

D = default features
F = user-specified features without - prefix
N = user-specified features with - prefix

Currently:

features = D ∪ F

Proposed:

features = (D ∖ N) ∪ F

e.g. given

[features]
default = ["foo", "bar"]
foo = []
bar = []
quz = []

--features=-bar,quz is desuraged to --no-default-features --features=foo,quz

@CAD97
Copy link
Contributor

CAD97 commented Jul 20, 2022

This would also be very useful for testing e.g. --all-features --features -legacy-compat.

(Though ofc you do need to worry about specifying the interaction with feature dependencies.)

@epage
Copy link
Contributor

epage commented Jul 20, 2022

Going to do a quick brain dump since this has been on my mind...

When it comes to evolving features without a breaking change, the biggest hazard is taking functionality that already existed and making a new feature from it. Existing features can already be split if you are ok with not reusing existing names though deprecation support would help a lot.

In cargo's existing model of default-features=false, existing functionality that gets split into a feature is being split out of the "base" functionality. rust-lang/rfcs#3283 works to solve this by adding an explicit "base" concept with a no-default-features feature that you can add to.

If we had started from scratch inventing default features, another approach to breaking existing functionality into a feature would be if we allow subtracting features from default. rust-lang/rfcs#3146 called this out as an alternative but it has the following challenges

  • It takes a lot of work to get people into the additive-feature mental model that this detracts from that messaging
  • Issues related to other things activating the de-activated feature
  • If we allowed general feature opt-out, it could likely break crates as they might check for a feature and assume its required features are available.

I think limiting the scope of feature opt-outs and ensuring design keeps the intent clear can overcome these challenges. I also think we can transition to this even if we didn't start with this.

A rough outline of my proposal

  • Further entrench defaults special status by deviating from normal feature behavior
    • Placeholder name for this kind of feature is "meta-feature"
    • Deprecate default from including dep/feat features, possibly turning into an error in future editions. It can only include features that the dependent crate can reference
    • Provide a syntax for opting out of default features in manifest and on the command line
      • Errors if the feature is not currently a default feature in the dependency
        • This means you can't reference features that will eventually exist which would be wanted for maximum version compatibility
      • Explicit non-goal to force/assert the deactivation of features
      • Explicit non-goal to deactivate features from features other than default
    • Stop providing the default feature to the compiler on an edition boundary, preventing people from being able to do #[cfg(feature = "default")]
  • Deprecate default-features = <bool> from the manifest,, possibly turning into an error in future editions

It would be interesting to see if this would help with --all-features but I would not see that as blocking on the design.

Of course, there are details to be worked out and bike shedding to be done.

Other benefits

  • Help with no-std
  • Help with "I want all features but...", like with clap

Open questions

@CAD97
Copy link
Contributor

CAD97 commented Jul 20, 2022

If we allowed general feature opt-out, it could likely break crates as they might check for a feature and assume its required features are available.

FWIW I would expect -foo to disable any features that have foo as a requirement (but other features that were activated because of it to stay on).

Explicit non-goal to force/assert the deactivation of features

Yes, it's removing the requirement, not the feature itself.


I agree that opting out of future default-features is unfortunate. But rather than move to a default feature, it seems like we could "just" allow opting out of default-features's specified features, and discourage/deprecate setting no-default-features for a manifest dependency.

A default feature then can be just a pattern for allowing opting out of a chunk in one opt-out like today's no-default-features, rather than cargo-recognized.

@epage
Copy link
Contributor

epage commented Jul 21, 2022

FWIW I would expect -foo to disable any features that have foo as a requirement (but other features that were activated because of it to stay on).

For this to work, it could only have that affect within the current crate's resolving of features. I worry these semantics are too close in semantics to asserting that a feature won't be present at all (remove requirement everywhere) that it could be confusing.

I also think it has the chance to break people. I'm going to expand on a case in RFC 3146. Say I have a dependency with

[features]
default = ["foo", "bar"]
foo = []
bar = []

and I depend on it with ["foo", "-bar"]

And later the dependency changes it to

[features]
default = ["foo", "bar"]
foo = []
bar = ["foo"]

Then according to these semantics, foo will be disabled on upgrade, breaking dependent crates. This is why the earlier proposal was looking to special case default's semantics into what I'm terming a meta-feature so only default will be in a quasi-state because we only modify its requirements but no one will be allowed to observe that quasi-state, making it work out.

@CAD97
Copy link
Contributor

CAD97 commented Jul 21, 2022

Then according to these semantics, foo will be disabled on upgrade

not by my intuitive understanding, though explaining it is a bit difficult. -bar would disable bar and default, but not foo, even if foo was not explicitly enabled.

.... also wait we set cfg(feature = "default") I did not know nor expect that

@CAD97
Copy link
Contributor

CAD97 commented Jul 21, 2022

explaining it

  • Collect feature requests by the current mechanism
  • For each - feature
    • Remove the feature request
    • Remove the feature requests which enable the removed feature, recursively
      • (other features implied by the superfeature remain; this is only possible for default with the below error)
  • If any feature removed in the previous was explicitly requested, error
    • Possible exception for --all-features or other "soft" ways of enabling features

@epage
Copy link
Contributor

epage commented Jul 21, 2022

.... also wait we set cfg(feature = "default") I did not know nor expect that

I assumed we didn't either but I went and tested it. We only set it if the user explicitly mentions a default feature

@epage
Copy link
Contributor

epage commented Jul 21, 2022

If any feature removed in the previous was explicitly requested, error

Wouldn't this still be a semver breakage for bar to require foo? Without the error, we are disabling foo when code was written assuming it was enabled. With an error, we went from a working build to a failing build on cargo update since foo will have been removed because bar was removed and foo was explicitly requested.

@epage
Copy link
Contributor

epage commented Aug 6, 2022

At RustConf, one of the problems that was pointed out is that default-features = false would be removed on an edition boundary of the dependent so a dependency cannot guarantee when all of their dependents are on the new edition to start relying on the new semver semantics.

Quick thoughts

@epage epage added the S-needs-rfc Status: Needs an RFC to make progress. label Sep 28, 2023
@epage epage changed the title Feature Request: Ability to disable single default features Ability to disable individual default features Sep 28, 2023
gz added a commit to gz/chrono that referenced this issue Dec 14, 2023
This is due to the mutually exclusive features in rkyv which
we expose now. `--all-features` will now activate them and the crate
will fail to compile rkyv. We work around this by defining
an explicit list of all mutually exclusive features to.

Unfortunately there isn't an easy way to share env variables
among different YAML files
(actions/runner#655).

There also isn't a good way to specify --all-features minus
"just a few" (rust-lang/cargo#3126)
aside from giving the complete list.

Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
gz added a commit to gz/chrono that referenced this issue Dec 14, 2023
This is due to the mutually exclusive features in rkyv which
we expose now. `--all-features` will now activate them and the crate
will fail to compile rkyv. We work around this by defining
an explicit list of all mutually exclusive features to.

Unfortunately there isn't an easy way to share env variables
among different YAML files
(actions/runner#655).

There also isn't a good way to specify --all-features minus
"just a few" (rust-lang/cargo#3126)
aside from giving the complete list.

Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
gz added a commit to gz/chrono that referenced this issue Dec 14, 2023
This is due to the mutually exclusive features in rkyv which
we expose now. `--all-features` will now activate them and the crate
will fail to compile rkyv. We work around this by defining
an explicit list of all mutually exclusive features to.

Unfortunately there isn't an easy way to share env variables
among different YAML files
(actions/runner#655).

There also isn't a good way to specify --all-features minus
"just a few" (rust-lang/cargo#3126)
aside from giving the complete list.

Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
gz added a commit to gz/chrono that referenced this issue Dec 14, 2023
This is due to the mutually exclusive features in rkyv which
we expose now. `--all-features` will now activate them and the crate
will fail to compile rkyv. We work around this by defining
an explicit list of all mutually exclusive features to.

Unfortunately there isn't an easy way to share env variables
among different YAML files
(actions/runner#655).

There also isn't a good way to specify --all-features minus
"just a few" (rust-lang/cargo#3126)
aside from giving the complete list.

Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
gz added a commit to gz/chrono that referenced this issue Dec 27, 2023
This is due to the mutually exclusive features in rkyv which
we expose now. `--all-features` will now activate them and the crate
will fail to compile rkyv. We work around this by defining
an explicit list of all mutually exclusive features to.

Unfortunately there isn't an easy way to share env variables
among different YAML files
(actions/runner#655).

There also isn't a good way to specify --all-features minus
"just a few" (rust-lang/cargo#3126)
aside from giving the complete list.

Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
gz added a commit to gz/chrono that referenced this issue Dec 27, 2023
This is due to the mutually exclusive features in rkyv which
we expose now. `--all-features` will now activate them and the crate
will fail to compile rkyv. We work around this by defining
an explicit list of all mutually exclusive features to.

Unfortunately there isn't an easy way to share env variables
among different YAML files
(actions/runner#655).

There also isn't a good way to specify --all-features minus
"just a few" (rust-lang/cargo#3126)
aside from giving the complete list.

Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
gz added a commit to gz/chrono that referenced this issue Dec 27, 2023
This is due to the mutually exclusive features in rkyv which
we expose now. `--all-features` will now activate them and the crate
will fail to compile rkyv. We work around this by defining
an explicit list of all mutually exclusive features to.

Unfortunately there isn't an easy way to share env variables
among different YAML files
(actions/runner#655).

There also isn't a good way to specify `--all-features` minus
"just a few" (rust-lang/cargo#3126)
aside from giving the complete list.

Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
gz added a commit to gz/chrono that referenced this issue Dec 27, 2023
This is due to the mutually exclusive features in rkyv which
we expose now. `--all-features` will now activate them and the crate
will fail to compile rkyv. We work around this by defining
an explicit list of all mutually exclusive features to.

Unfortunately there isn't an easy way to share env variables
among different YAML files
(actions/runner#655).

There also isn't a good way to specify `--all-features` minus
"just a few" (rust-lang/cargo#3126)
aside from giving the complete list.

Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
gz added a commit to gz/chrono that referenced this issue Dec 27, 2023
This is due to the mutually exclusive features in rkyv which
we expose now. `--all-features` will now activate them and the crate
will fail to compile rkyv. We work around this by defining
an explicit list of all mutually exclusive features to.

Unfortunately there isn't an easy way to share env variables
among different YAML files
(actions/runner#655).

There also isn't a good way to specify `--all-features` minus
"just a few" (rust-lang/cargo#3126)
aside from giving the complete list.

Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
gz added a commit to gz/chrono that referenced this issue Dec 27, 2023
This is due to the mutually exclusive features in rkyv which
we expose now. `--all-features` will now activate them and the crate
will fail to compile rkyv. We work around this by defining
an explicit list of all mutually exclusive features to.

Unfortunately there isn't an easy way to share env variables
among different YAML files
(actions/runner#655).

There also isn't a good way to specify `--all-features` minus
"just a few" (rust-lang/cargo#3126)
aside from giving the complete list.

Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
djc pushed a commit to chronotope/chrono that referenced this issue Dec 29, 2023
This is due to the mutually exclusive features in rkyv which
we expose now. `--all-features` will now activate them and the crate
will fail to compile rkyv. We work around this by defining
an explicit list of all mutually exclusive features to.

Unfortunately there isn't an easy way to share env variables
among different YAML files
(actions/runner#655).

There also isn't a good way to specify `--all-features` minus
"just a few" (rust-lang/cargo#3126)
aside from giving the complete list.

Signed-off-by: Gerd Zellweger <mail@gerdzellweger.com>
@djc
Copy link
Contributor

djc commented Mar 25, 2024

I just ran into this again: rustls (and, downstream of that, tokio-rustls) switched the default crypto provider in their latest releases (0.23 and 0.26 respectively), with the new releases allowing downstream users to switch crypto provider dependencies via Cargo features. I upgraded tokio-rustls and used default-features = false, features = ["ring"] to go back to the "old" default. However, this in turn also disabled other default features, including tls12, which might have caused our proxy endpoint to become harder to reach for many kinds of HTTP clients. Would be really nice to have this feature.

@epage
Copy link
Contributor

epage commented Mar 25, 2024

I think there is solid interest in this. The problem is availability for someone to go through the design process. For myself, I already have about a years worth of design work I've committed to.

(this is in a similar boat to mutually exclusive, global features)

@djc
Copy link
Contributor

djc commented Mar 25, 2024

So like, start with a pre-RFC on internals and work from there?

@epage
Copy link
Contributor

epage commented Mar 25, 2024

Yes, though first making sure to read over this thread so any nuance in it gets captured.

@tbu-
Copy link
Contributor

tbu- commented Mar 30, 2024

I don't think a third-party solution will be accepted by the Cargo team.

My two-year old rust-lang/rfcs#3283 would have solved it, in a way that other people are already able to hack around Cargo's lack of proper support for default-features: https://slint.dev/blog/rust-adding-default-cargo-feature. It was closed for being too complex, but I think it's the minimum complexity to make such a feature work at all. Another approach at rust-lang/rfcs#3347 has stalled for 1.5 years.

@kornelski
Copy link
Contributor

To make it clearer to users that this doesn't disable features globally I suggest a couple of mitigations:

  1. Give it a verbose descriptive syntax, such as "default-without:foo".

  2. Warn when these features end up enabled anyway. Now that there's [lints] it can be used to acknowledge it if necessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-features Area: features — conditional compilation C-feature-request Category: proposal for a feature. Before PR, ping rust-lang/cargo if this is not `Feature accepted` S-needs-rfc Status: Needs an RFC to make progress.
Projects
None yet
Development

No branches or pull requests