# Feature Request: "unstable"/"opt-in"/"non-transitive" crate features

**URL:** <https://internals.rust-lang.org/t/feature-request-unstable-opt-in-non-transitive-crate-features/16193>\
**Category:** cargo\
**Created:** [February 21, 2022, 10:25pm UTC](https://internals.rust-lang.org/t/feature-request-unstable-opt-in-non-transitive-crate-features/16193 "2022-02-21T22:25:58Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![NfNitLoop](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/nfnitloop/32/9099_2.png) [@NfNitLoop](https://internals.rust-lang.org/u/NfNitLoop)\
**Post date:** [February 21, 2022, 10:25pm UTC](https://internals.rust-lang.org/t/feature-request-unstable-opt-in-non-transitive-crate-features/16193/1 "2022-02-21T22:25:58Z")

</div>

Hello! The GitHub [feature request template](https://github.com/rust-lang/cargo/blob/46c9b51957e335ad03051ae8c513f3ba2a55b3b2/.github/ISSUE_TEMPLATE/feature_request.yml) directed me here.

I don't keep up with the forums here (yet!) so this may already be a feature that's being discussed or proposed elsewhere -- if that's the case, please point me in the right direction. 🙂

## Context:

I'm a new user of Tokio. It's got this great new JoinSet that I'd like to use. However, it's still a bit experimental, so it's part of their [unstable API](https://docs.rs/tokio/latest/tokio/index.html#unstable-features), which must be enabled with `--cfg tokio_unstable`.

I've been using Rust for a few years, but so far I've never had to directly apply compiler flags, so I had to go and figure out how to do that. I ran into a couple issues along the way:

- Despite [seeing some examples in the wild](https://github.com/search?q=rustflags+filename%3ACargo.toml&ref=advsearch&type=Code), `rustflags` does not seem to actually work from Cargo.toml.  
(To be fair, Cargo did warn about an "unused manifest key". But of course, I'm doing this while I'm in the middle of hacking on a project so there were so many _other_ warnings that I didn't notice until much later.)
- If you specify a `cfg` key that doesn't exist, there's no error or warning. (And can be no warning. Because that `--cfg foo` is applied to all `rustc` invocations.) It just seems to not work and the user is left scratching their head.

## Why Not a Crate Feature?

So, frustrated, I head over to the Tokio Discord for help, and immediately [figure out my own problem](https://discord.com/channels/500028886025895936/500336333500448798/945419200321683527). 🤦‍♂️

But we also had a chat about why this isn't exposed as a feature:

In Cargo, features are transitive. If `foo` depends on `bar`, and `bar` enables `unstable_tokio`, `foo` will get that feature enabled silently. `Foo` doesn't don't know that `bar` is likely to break in the future. And `foo` may also accidentally depend on unstable features.

To avoid that behavior, the Tokio developers used this `cfg` flag which must be specified as part of the top-level build, so that end users always **opt in explicitly**.

In searching for `rustflags` in Cargo.toml above, I found out that the uuid-rs crate [uses the same pattern](https://github.com/uuid-rs/uuid/blob/5aa53c880c30b811fc29917c837c7cd0d34c7c8d/Cargo.toml#L87-L88) for unstable features. Maybe this is a commonly known and used pattern?

## Finally, The Feature Request:

It seems like folks are using unstable `--cfg` flags to work around a shortcoming in Cargo. It would be nice if Cargo could handle this, so we could just use feature flags!

Benefits:

- Useable from `Cargo.toml`
- Cargo can check if you reference a feature that's not (or no longer) present, or a typo.
- Makes it easier to follow semver rules by testing out unstable features before making them part of a stable feature set.

My initial (TBH not very well thought-out) brainstorm:

**A new feature type**. It could be called "opt-in features", or "non-transitive features", but "unstable" seems to describe this particular use case, so I'll use that here.

**Within** a crate, unstable features work just like stable ones. ex:

```toml
# file: foo/Cargo.toml

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

# Defining an unstable feature:
[unstable_features]
unstable_bar = ["baz"]

# Using unstable features in direct dependencies:
[dependencies.third_party]
version = "*"
features = ["unstable_whatever"]

```

The new "unstable feature" functionality become apparent when transitive dependencies come into play:

```toml
# file: my-proj/Cargo.toml (errors)

[dependencies.foo]
version = "*"

```

This would yield some error like:

> Crate 'foo' has been configured to use the following unstable features:
> 
> - crate: foo, features = ["unstable\_bar"]
> - crate: third\_party, features = ["unstable\_whatever"]
> 
> Unstable features may break semver guarantees, so require explicit opt-in. See: [docs].

And to fix it, the end user just **explicitly opts in** to the unstable features in their Cargo.toml:

```toml
# file: my-proj/Cargo.toml (fixed)

[dependencies.foo]
version = "*"
# Opt in to direct dependency's unstable feature. 
features = ["unstable_bar"]

# Must also opt into all transitive unstable features:
[dependencies.third_party]
version = "*"
features = ["unstable_whatever"]

```

Other possibilities:

- If we're worried about a scourge of unstable features showing up in crates, `cargo publish` could deny crates that have unstable features enabled in their default configuration.
- Might want to force a prefix (`unstable_`?) for these types of feature flags.
- Maybe add some metadata to unstable features, like a link to docs about why they're unstable?

---

<div class="post-metadata">

**Author:** ![NfNitLoop](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/nfnitloop/32/9099_2.png) [@NfNitLoop](https://internals.rust-lang.org/u/NfNitLoop)\
**Post date:** [February 22, 2022, 1:18am UTC](https://internals.rust-lang.org/t/feature-request-unstable-opt-in-non-transitive-crate-features/16193/2 "2022-02-22T01:18:14Z")

</div>

Alternatively, maybe "unstable" is just a piece of metadata you can add to a feature. We could enable a slightly different feature declaration syntax:

```nohighlight
[features]
default = ["foo"]
foo = []

[features.bar]
unstable = true
enables = ["foo"]

# This would even allow additional metadata, like a description:
# Would be handy to compile these into rustdoc!
description = "enables barring foos, which is unstable until we figure out X."

```

---

<div class="post-metadata">

**Author:** ![mathstuf](https://avatars.discourse-cdn.com/v4/letter/m/958977/32.png) [@mathstuf](https://internals.rust-lang.org/u/mathstuf)\
**Post date:** [February 22, 2022, 12:11pm UTC](https://internals.rust-lang.org/t/feature-request-unstable-opt-in-non-transitive-crate-features/16193/3 "2022-02-22T12:11:54Z")

</div>

Non-transitive has a problem with `impl Trait for N`. Let's say there are crates A, B, C, and D. A provides some type K with an `impl SomeTrait` behind a feature flag. B requests the feature of A, C does not, and D uses A (with the feature), B, and C. If there is a function `f` (in any crate) that has a `T: SomeTrait` bound.

Questions:

- If `f` is in crate `C`, can `D` pass a `K` to the function?
- What if `D` gives `f` to `B` to then call with `K`?
- If it can be called, is this passed-in `K` the same TypeId as `C`'s lookup of `K`?
- If it cannot be called, what kinds of error messages would make any sense when this happens?

I think hiding _names_ (anything you can name with `use`) behind "you didn't ask for this feature" is fine, but acting as if the feature is completely gone is not feasible. You cannot hide an `impl` behind a `use` statement (i.e., it is global state), so it cannot be hidden by such feature flag logic.

---

<div class="post-metadata">

**Author:** ![Aloso](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/aloso/32/5039_2.png) [@Aloso](https://internals.rust-lang.org/u/Aloso)\
**Post date:** [February 22, 2022, 1:08pm UTC](https://internals.rust-lang.org/t/feature-request-unstable-opt-in-non-transitive-crate-features/16193/4 "2022-02-22T13:08:58Z")

</div>

Marking some parts of the API as "unstable" can be useful. For example, clap 3.1.1 does it with cargo features that start with `unstable-`, which is okay because clap is usually only used in binaries. For crates that are used in libraries, we have the problem you described: You don't just get the features you enabled, but the union of the features enabled by you and your dependencies. This is why features are called **additive**.

This can cause problems, because a crate can use features of a dependency which it didn't explicitly enable. Let's say, the feature in question is called `feat`, which is enabled by a crate called `dep`. If `dep` publishes a new version that no longer enables `feat`, upgrading to that version might break your build. The solution is to explicitly enable all the features you use. Unfortunately, Rust can't assist in detecting this issue.

With unstable features, we have the same problem: When a crate enables a feature `unstable-feat`, another crate in the same dependency graph can use the feature even if it didn't enable it.

What you propose is an interesting solution: Make it illegal to enable an unstable feature _by default_. This means that the root crate has to opt into unstable features of _all_ its transitive dependencies. This works, because dependencies can forward features of their dependencies:

```nohighlight
[unstable-features]
unstable-feat = ["dep1/unstable-feat", "dep2/unstable-feat"]

```

This has the advantage that we always know when there are unstable features enabled somewhere in the dependency graph. This is significant, because unstable features can be removed or changed in a minor version. If we use an unstable feature, upgrading the dependencies might break our build, so we have to track our lockfile.

However, this doesn't completely fix the underlying issue: If we want to use a crate `dep1` that enables an unstable feature in `dep2`, we have to enable it explicitly. But if we also depend on `dep2`, the unstable feature is enabled implicitly.

---

<div class="post-metadata">

**Author:** ![steffahn](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/steffahn/32/13288_2.png) [@steffahn](https://internals.rust-lang.org/u/steffahn)\
**Post date:** [December 22, 2024, 5:22pm UTC](https://internals.rust-lang.org/t/feature-request-unstable-opt-in-non-transitive-crate-features/16193/5 "2024-12-22T17:22:12Z")

</div>

This topic was automatically closed 540 days after the last reply. New replies are no longer allowed.
