From 1749d114c2da33a311a2c151bd4945f608d544d7 Mon Sep 17 00:00:00 2001 From: jyn Date: Fri, 21 Nov 2025 21:39:13 -0500 Subject: [PATCH 01/25] `RUSTC_ALLOW_UNSTABLE_`: a `RUSTC_BOOTSTRAP` alternative This RFC was in large part drafted in May 2025, shortly after my Rust All Hands talk, "Futile Feature Gates". There were about 25 people in that room; I want to thank all of them for their feedback during that session, and to everyone who reviewed early drafts of this RFC. --- text/0000-rustc-bootstrap.md | 157 +++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 text/0000-rustc-bootstrap.md diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md new file mode 100644 index 00000000000..ce64f48c663 --- /dev/null +++ b/text/0000-rustc-bootstrap.md @@ -0,0 +1,157 @@ +- Feature Name: `RUSTC_ALLOW_UNSTABLE` +- Start Date: 2025-05-18 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +This RFC proposes decoupling the two components of our stability policy: still requiring feature gates, but allowing feature gates to be enabled on stable. It does so by adding `RUSTC_ALLOW_UNSTABLE_` environment variables which can be used to permit using feature gates on stable toolchain channels. These variable are respected by all official Rust tools that use feature gates. + +This RFC is *not* intended as a general purpose mechanism for Rust developers to use nightly features on stable; it's specifically targeted at build systems wrapping cargo, such as distro packagers, external tools shipped with the toolchain, and large projects that build a custom Rust toolchain from source. + +# Motivation +[motivation]: #motivation + +## Why allow using unstable features on stable? + +Rust's stability policy has two components: +1. To the extent possible, each unstable feature comes with a feature gate, and is disabled when that feature gate is inactive. [^1] +2. Enabling feature gates is only allowed on the nightly toolchain. + +[^1]: There are some exceptions to this, such as https://github.com/rust-lang/rust/issues/139892#issuecomment-2808505610. But in general we attempt to make sure all unstable features have a feature gate. + +Our motivation for 1 (having feature gates) is to make sure that people do not unknowingly rely on unstable features. This was a big problem for e.g. intra-doc links, which [people often used without knowing they were unstable][63305], making it impossible to remove the feature. + +[63305]: https://github.com/rust-lang/rust/issues/63305 + +Our motivation for 2 (disabling feature gates on stable) is three-fold: +1. Prevent people from relying on features that may change in the future while on the stable toolchain, upholding our "stability without stagnation" motto. +2. Disallow library authors from "silently" opt-ing in to unstable features, such that the person running the top-level build doesn't know they're using unstable features that may break when the toolchain is updated. This rationale doesn't apply to nightly, where the party running the top-level build is assumed to know that nightly comes with no stability guarantees. +3. Encourage people to help stabilize the features they care about. + +There are some cases in which none of those goals are applicable, but we still prevent people from using nightly features. This is particularly bad when projects *must* depend on unstable features to ship another feature they care about. Some examples: +- rust-analyzer and RustRover need `./some-libtest-binary --format=json` to determine the list of possible tests to run +- rust-analyzer and RustRover need all values in `rustc --print=cfg` to build the standard library (see [#139892](https://github.com/rust-lang/rust/issues/139892#issuecomment-2808505610) for an explanation of why this is affected by unstable features) +- `cargo semver-checks` needs `rustdoc --output-format=json` in order to work at all +- Rust for Linux needs a way to build a custom version of core. In particular, they mentioned they need to disable float support, because using float registers can cause unsoundness. + +Why are these uses ok? Two reasons: +- Each of these tools accept responsibility for breakage. `semver-checks` and RfL both explicitly adapt to each new release of rustc, and their feedback on breakage is very useful for improving the features they use. rust-analyzer and RustRover don't break at all for `--print=cfg`—they're not using it in code, only in the CLI—and adapt to any changes in libtest json format. +- These tools act as a "buffer" between other projects and breakage. For example, semver-checks hides the breaking changes behind its own interface such that downstream projects are not affected. Similar, Rust for Linux backports breakage fixes to stable branches such that old versions of the kernel keep building with new rust toolchains. + +One might ask, well, maybe we are being too eager to gate things, but can't people just use nightly? There are some cases where switching to nightly is not realistic. +- When using rustc packaged by a distro (e.g. Fedora or `nixpkgs`), only the stable channel is packaged. +- Tools that wrap the compiler (e.g. `rust-analyzer` or `cargo expand`) or libraries (e.g. `proc-macro2`) usually do not control the toolchain version being used. +- Stable with `RUSTC_BOOTSTRAP` is not the same as nightly. In particular, stable contains backports and nightly does not. + +## Why this exact mechanism? + +Currently, these tools use [`RUSTC_BOOTSTRAP=1`][rustc-bootstrap] as a workaround. But this workaround has many downsides: + +[rustc-bootstrap]: https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/rustc-bootstrap.html + +- Enabling RUSTC_BOOTSTRAP for one part of the toolchain enables it for *all* parts of the toolchain; in particular: + - `proc-macro2` uses `cargo:rerun-on-env-changed=RUSTC_BOOTSTRAP`, causing cache thrashing whenever this env var changes. + - rust-analyzer wants to enable RUSTC_BOOTSTRAP only for cargo and libtest, but the variable enables features for rustc as well. `RUSTFLAGS="-Z allow-features="` fixes this for lang features, but at the price of thrashing the cache; and there is no equivalent way to disable unstable CLI features. +- Libraries that detect RUSTC_BOOTSTRAP sometimes do it incorrectly (in particular, `-Zallow-features` often messes things up). To do this correctly, one must compile a full rust program that uses the api the library wants to enable; but in practice doing this is rare. Using more specific environment variables makes it less likely that a single misbehaving library can break the whole build. + +An important design constraint here is that the "end-user" (whoever is running the build) should always have control over which features are enabled. To the extent that tools act as a "buffer" between feature breakage and the end-user, they should only take responsibility for exactly the features whose breakage they know how to handle. + +We continue to use environment variables because that only permits the top-level build to allow feature gates. Cargo has `[env]` blocks, but they are only enabled for the top-level build, and it's usually easy for wrapping build systems to ignore `.cargo/config.toml` files. As a side effect, `[env]` makes it easy for developers to experiment locally with nightly features on a stable toolchain, but without allowing them to opt-in silently when their library is published to crates.io. This seems good, actually. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +The following documentation will live in the [unstable book], not in the [rustc book]. + +[unstable book]: https://doc.rust-lang.org/nightly/unstable-book/ +[rustc book]: https://doc.rust-lang.org/nightly/rustc/ + +The `RUSTC_ALLOW_UNSTABLE_*` environment variables allow you to use unstable features on the stable and beta channels. In particular, it allows you to use `-Z` flags and `#![feature(..)]` attributes. + +**NOTE:** This was previously done using a single `RUSTC_BOOTSTRAP` environment variable. **Please** avoid using `RUSTC_BOOTSTRAP`; it causes the Rust Project maintainers many issues. These variables are designed to replace all places where you might need it. + +Each variable is named after the feature or CLI flag it enables. Tools are pseudo-namespaced. For example, `RUSTC_ALLOW_UNSTABLE_VALIDATE_MIR=1` allows using `rustc -Z validate-mir`, `RUSTC_ALLOW_UNSTABLE_ASM_UNWIND` allows using `#![feature(asm_unwind)]`, and `RUSTC_ALLOW_UNSTABLE_RUSTDOC_OUTPUT_FORMAT` allows using `rustdoc --output-format=json`. + +Each variable takes one of three arguments: +1. `1` indicates that all crates can use the feature. This is the default on nightly. +2. `-1` indicates that no crates can use the feature. This is the default on stable and beta, but can be useful when using a nightly toolchain. +3. `ident` indicates that only the crate named `ident` can use the feature. Multiple crates can be specified at once by separating them with commas; for example, `RUSTC_ALLOW_UNSTABLE_ASM_UNWIND=tokio,hyper` allows specifying `#![feature(asm_unwind)]` in the `tokio` and `hyper` crates. + +Note that on most platforms, it is impossible to set an environment variable more than once, so be careful not to overwrite any existing variable. + +## Stability policy + +Despite being usable on stable, this is an unstable feature. Like any other unstable feature, we reserve the right to change or remove this feature in the future, as well as any other unstable feature that it enables. Using this feature is opting out of the normal stability/backwards compatibility guarantee of stable. + +Although we do not take technical measures to prevent it from being used, we strongly discourage using this feature. If at all possible, please contribute to stabilizing the features you care about instead of bypassing the Rust project's stability policy. + +If you do use this to enable an unstable feature, please contact a member of the project who works on the feature in question, so that we know who is exposed to breakage. For example, if you are using `RUSTC_ALLOW_UNSTABLE_RUSTDOC_OUTPUT_FORMAT=1`, reach out to [Alona Enraght-Moony][alona] (the maintainer of rustdoc-json). If you do not know who to contact, ask on [Zulip]. Contacting a maintainer provides no stability guarantees and does not mean the maintainer will agree to work with you, but can help us find a alternative solution to your problem or otherwise improve the unstable feature you are using. + +[alona]: https://github.com/aDotInTheVoid/ +[Zulip]: https://rust-lang.zulipchat.com/ +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +Each environment variable name is determined as follows: +1. Start with the prefix `RUSTC_ALLOW_UNSTABLE_` +2. Add the name of the tool in uppercase followed by an underscore, if present. For example, clippy would append `CLIPPY_`. For the purpose of this RFC, any generated libtest binaries are counted as a tool and append `TEST_`. +3. Append the feature name. + a. For language and library features, append the name of the feature gate in uppercase. For example, `#![feature(asm_unwind)]` would append `ASM_UNWIND`. + b. For cli flags, append the name of the flag in uppercase, excluding any `-Z` prefix, and replacing dashes with underscores. For example, `-Z validate-mir` would append `VALIDATE_MIR`. + +As a quality-of-implementation concern, the tool may warn when a `crate_name` passed to an environment variable is not a valid Rust identifier (this may happen if, e.g., a cargo package name is used instead of the proper crate name). + +As a quality of implementation concern, the tool should warn when an unrecognized feature is permitted. + +# Drawbacks +[drawbacks]: #drawbacks + +- This encourages using unstable features on stable, explicitly going against our goals as a project. But people are doing that anyway, and keeping the status quo does not help us prevent them, while causing many other issues. +- Rustfmt is often run automatically by editor plugins, not explicitly. Additionally, right now rustfmt warns and continues when a feature gate is enabled on stable, which means the whole codebase gets reformatted. We should make sure rustfmt is changed to instead give a hard error when the environment variable is missing, which will avoid editors accidentally reformatting the whole codebase. [The rustfmt team intends to fix this](https://github.com/rust-lang/rustfmt/issues/5022). +- This may make it less likely that people help stabilize features. But stabilizing features is [very very hard](https://medium.com/@ElizAyer/organizational-boundary-problems-too-many-cooks-or-not-enough-kitchens-2ddedc6de26a), and in the meantime people have very little recourse when they need to use an unstable feature. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +- We could allow specifying individual values of a CLI flag, not just the name of the value. For example, this could be useful for libtest's `--format` flag, to only allow `--format=json` but not `--format=junit`. I think in practice it will not cause issues to lump these together. We always have the option to add more variables in the future; and because this whole feature is perma-unstable, we have the option to rename existing vars as well. +- We could have a single environment variable which takes the feature name as a value. That does not allow composing the variables - setting it in one place breaks all other places that set it. Additionally, it does not allow build scripts to do fine-grained caching, because cargo only has `rerun-on-env-changed` and not anything more granular. +- We could use CLI flags instead of an environment variable. That breaks caching, because rust-analyzer cannot pass `RUSTFLAGS=-Zallow-features` without cargo rebuilding, and the cargo team does not wish to inspect the contents of RUSTFLAGS. +- We can """simply""" tell people to stop using nightly features on stable (either politely, or with technical measures). This will have a large negative impact on the ecosystem - rust-analyzer and RustRover will not support running unit tests on stable; `cargo semver-checks` will not work at all on stable; Rust for Linux will break entirely. +- We can leave the status quo. This is in many ways the worst of all worlds - people still use unstable features on stable, but in hacky ways that break. +- We can separate the toolchain into "stable" and "unstable" channels, and tell distros to package both. This is a big ask from distros, and does not actually help with many of the problems (for example cargo semver-checks cannot rely on it being installed, and rust-analyzer will still have caching issues). + +This cannot be done in a library or macro. +# Prior art +[prior-art]: #prior-art + +- Go has the [`goexperiment` module]. This is enabled at compile time with an environment variable that takes a list of features to enable. +- Java has implementation-specific [`-X` flags][java-x] (which are roughly equivalent to `-Z` flags in Rust). They do not have feature gates. Java also has [preview features], which are guaranteed to exist in all implementations, but require opting in with `--enable-preview` *both* at compile time (with `javac`) and at runtime (with the `java` binary). +- Python distributes separate binaries that [disable the GIL by default][free-threaded python]. Python also has [`-X` flags][python-x], which do not have feature gates. +- Scala allows marking library APIs as [experimental]. Experimental APIs are "infectious" - any code using an experimental API must also be marked as experimental. Additionally, experimental APIs can be upgraded to [preview], meaning that they are guaranteed to exist in the future but might change their exact details. Unlike experimental APIs, preview APIs are not infectious. To enable experimental/preview features for all functions in a module at once, the compiler takes `-experimental`/`-preview` flags. +- Kubernetes allows [enabling features][kubernetes-features] with `--feature-gates=Feature1=true,Feature2=true`. Additionally, it splits features into "Alpha" (experimental, can be removed altogether) and "Beta" (enabled by default, tested, can be changed but not removed). + +[`goexperiment` module]: https://pkg.go.dev/internal/goexperiment +[java-x]: https://docs.oracle.com/cd/E13150_01/jrockit_jvm/jrockit/jrdocs/refman/optionX.html +[preview features]: https://docs.oracle.com/en/java/javase/22/language/preview-language-and-vm-features.html +[python-x]: https://docs.python.org/3/using/cmdline.html#cmdoption-X +[free-threaded python]: https://docs.python.org/3/whatsnew/3.13.html#whatsnew313-free-threaded-cpython +[kubernetes-features]: https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/ +[experimental]: https://docs.scala-lang.org/scala3/reference/other-new-features/experimental-defs.html +[preview]: https://docs.scala-lang.org/scala3/reference/preview/index.html# + +# Unresolved questions +[unresolved-questions]: #unresolved-questions +- Will this run into platform-specific limitations on [env variable lengths][limits.h] when many env variables are passed? The "minimum maximum" is 4096 but in practice most platforms seem to be much higher. Unlike flags, environment variables cannot be passed in [response files]. + +[limits.h]: https://pubs.opengroup.org/onlinepubs/009695399/basedefs/limits.h.html#:~:text=ARG_MAX +[response files]: https://doc.rust-lang.org/rustc/command-line-arguments.html#path-load-command-line-flags-from-a-path + +# Future possibilities +[future-possibilities]: #future-possibilities + +- Break hard on RUSTC_BOOTSTRAP now that people have an alternative. For example, we could remove the `RUSTC_BOOTSTRAP=crate_name` syntax and instead require `RUSTC_BOOTSTRAP=` of the commit rustc was built with. The goal here is for no one to use the variable except bootstrap itself. +- Rename RUSTC_BOOTSTRAP to a name that makes more sense, such as `RUSTC_ALLOW_ALL_FEATURES`. +- We could split unstable features into "alpha" and "beta", and only allow the latter to be enabled with `RUSTC_ALLOW_FEATURE`. Additionally, we could enable beta features by default on the beta channel. +- We could add a version scheme to unstable features, such that the opt-in has to specify exactly which version of the feature it expects (and gets a hard error if its expected version doesn't match the version implemented in the compiler). The syntax for the opt-in would look like `RUSTC_ALLOW_FEATURE_NAME=2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. To encourage contributors to bump the version, we could remind them (e.g. in a Github comment when a PR is opened) whenever a test that uses the feature is modified. From 6a24eb480082845188c8411a420371775767be4b Mon Sep 17 00:00:00 2001 From: jyn Date: Fri, 21 Nov 2025 23:00:25 -0500 Subject: [PATCH 02/25] typos --- text/0000-rustc-bootstrap.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index ce64f48c663..da04acd13af 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -153,5 +153,5 @@ This cannot be done in a library or macro. - Break hard on RUSTC_BOOTSTRAP now that people have an alternative. For example, we could remove the `RUSTC_BOOTSTRAP=crate_name` syntax and instead require `RUSTC_BOOTSTRAP=` of the commit rustc was built with. The goal here is for no one to use the variable except bootstrap itself. - Rename RUSTC_BOOTSTRAP to a name that makes more sense, such as `RUSTC_ALLOW_ALL_FEATURES`. -- We could split unstable features into "alpha" and "beta", and only allow the latter to be enabled with `RUSTC_ALLOW_FEATURE`. Additionally, we could enable beta features by default on the beta channel. -- We could add a version scheme to unstable features, such that the opt-in has to specify exactly which version of the feature it expects (and gets a hard error if its expected version doesn't match the version implemented in the compiler). The syntax for the opt-in would look like `RUSTC_ALLOW_FEATURE_NAME=2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. To encourage contributors to bump the version, we could remind them (e.g. in a Github comment when a PR is opened) whenever a test that uses the feature is modified. +- We could split unstable features into "alpha" and "beta", and only allow the latter to be enabled with `RUSTC_ALLOW_UNSTABLE`. Additionally, we could enable beta features by default on the beta channel. +- We could add a version scheme to unstable features, such that the opt-in has to specify exactly which version of the feature it expects (and gets a hard error if its expected version doesn't match the version implemented in the compiler). The syntax for the opt-in would look like `RUSTC_ALLOW_UNSTABLE_NAME=2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. To encourage contributors to bump the version, we could remind them (e.g. in a Github comment when a PR is opened) whenever a test that uses the feature is modified. From 52292164d5bd523cdf37bff0c5bf93ace0de09c2 Mon Sep 17 00:00:00 2001 From: jyn Date: Sat, 22 Nov 2025 08:52:08 -0500 Subject: [PATCH 03/25] clarify how ambiguity is resolved --- text/0000-rustc-bootstrap.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index da04acd13af..9330aba054c 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -100,10 +100,17 @@ Each environment variable name is determined as follows: 3. Append the feature name. a. For language and library features, append the name of the feature gate in uppercase. For example, `#![feature(asm_unwind)]` would append `ASM_UNWIND`. b. For cli flags, append the name of the flag in uppercase, excluding any `-Z` prefix, and replacing dashes with underscores. For example, `-Z validate-mir` would append `VALIDATE_MIR`. - + As a quality-of-implementation concern, the tool may warn when a `crate_name` passed to an environment variable is not a valid Rust identifier (this may happen if, e.g., a cargo package name is used instead of the proper crate name). -As a quality of implementation concern, the tool should warn when an unrecognized feature is permitted. +As a quality-of-implementation concern, the tool should warn when an unrecognized feature is permitted. + +As a quality-of-implementation concern, the compiler should verify (i.e. through testing, not at runtime) that no CLI flag name would cause its environment variable to overlap with a feature gate. +Existing feature gates that cause such a conflict should be renamed. +For example, `-Z ub-checks` and `feature(ub_checks)` cause an overlap under this proposal; `feature(ub_checks)` should be renamed to avoid the overlap. +Note that this is only relevant to the compiler, since other tools are already pseudo-namespaced and can't have conflicts. + +As a quality-of-implementation concern, the compiler should verify (through testing) that there are no conflicts between a compiler feature and an official tool feature; for example, it should verify that `feature(rustdoc_internals)` does not conflict with a rustdoc flag named `-Z internals`. # Drawbacks [drawbacks]: #drawbacks From e8a441de492a23fe1c9c1a43d56a382ec1e613f8 Mon Sep 17 00:00:00 2001 From: Jynn Nelson Date: Fri, 5 Dec 2025 13:38:03 -0500 Subject: [PATCH 04/25] mention rustc_public as an interested party --- text/0000-rustc-bootstrap.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 9330aba054c..5a6074172f3 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -35,6 +35,7 @@ There are some cases in which none of those goals are applicable, but we still p - rust-analyzer and RustRover need all values in `rustc --print=cfg` to build the standard library (see [#139892](https://github.com/rust-lang/rust/issues/139892#issuecomment-2808505610) for an explanation of why this is affected by unstable features) - `cargo semver-checks` needs `rustdoc --output-format=json` in order to work at all - Rust for Linux needs a way to build a custom version of core. In particular, they mentioned they need to disable float support, because using float registers can cause unsoundness. +- `rustc_public`'s entire mission is to wrap unstable APIs with stable ones and therefore needs access to all `rustc_private` features. Why are these uses ok? Two reasons: - Each of these tools accept responsibility for breakage. `semver-checks` and RfL both explicitly adapt to each new release of rustc, and their feedback on breakage is very useful for improving the features they use. rust-analyzer and RustRover don't break at all for `--print=cfg`—they're not using it in code, only in the CLI—and adapt to any changes in libtest json format. From c41b44ed699b4f89d5b173bebdd7375fa4c137a8 Mon Sep 17 00:00:00 2001 From: Jynn Nelson Date: Fri, 5 Dec 2025 13:38:15 -0500 Subject: [PATCH 05/25] talk about build scripts and caching --- text/0000-rustc-bootstrap.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 5a6074172f3..666b8d98de2 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -82,6 +82,15 @@ Each variable takes one of three arguments: Note that on most platforms, it is impossible to set an environment variable more than once, so be careful not to overwrite any existing variable. +## Build scripts + +Setting these is not allowed in build scripts and will cause the build to fail. + +Reading these variables and using them to do feature detection is allowed, but strongly discouraged. +Be sure to read the variables for *all* relevant features; respect `-Z allow-features`; and compile a real crate to make sure that your expected usage matches up with the version of the feature that's implemented. +Set `cargo::rerun-if-env-changed` for all feature gates that could possibly be enabled by your build script. +Do *not* simply check whether this is a nightly compiler or not. + ## Stability policy Despite being usable on stable, this is an unstable feature. Like any other unstable feature, we reserve the right to change or remove this feature in the future, as well as any other unstable feature that it enables. Using this feature is opting out of the normal stability/backwards compatibility guarantee of stable. @@ -95,6 +104,8 @@ If you do use this to enable an unstable feature, please contact a member of the # Reference-level explanation [reference-level-explanation]: #reference-level-explanation +## Determining variable names + Each environment variable name is determined as follows: 1. Start with the prefix `RUSTC_ALLOW_UNSTABLE_` 2. Add the name of the tool in uppercase followed by an underscore, if present. For example, clippy would append `CLIPPY_`. For the purpose of this RFC, any generated libtest binaries are counted as a tool and append `TEST_`. @@ -113,6 +124,17 @@ Note that this is only relevant to the compiler, since other tools are already p As a quality-of-implementation concern, the compiler should verify (through testing) that there are no conflicts between a compiler feature and an official tool feature; for example, it should verify that `feature(rustdoc_internals)` does not conflict with a rustdoc flag named `-Z internals`. +## Restricting variables in build scripts + +Currently, Cargo restricts setting `cargo::rustc-env=RUSTC_BOOSTRAP=1` from a build script. +Once this RFC is accepted, it will also restrict any variable starting with `RUSTC_ALLOW_UNSTABLE_`. This does not require coordination between the compiler and Cargo and so I do not expect it to be a high maintenance burden. + +## Caching + +Currently, changing `RUSTC_BOOTSTRAP` does not invalidate Cargo's build cache. +We suggest keeping this state of affairs so that setting a single variable does not require rebuilding the whole dependency tree; the whole point of not using `RUSTFLAGS` is to avoid rebuilding unnecessarily. +Cache invalidation should be done by build scripts setting `rerun-if-env-changed` if necessary. + # Drawbacks [drawbacks]: #drawbacks From c98d1da9a94a7311424ca468e17dc7a7cc1c4aae Mon Sep 17 00:00:00 2001 From: Jynn Nelson Date: Tue, 7 Apr 2026 15:57:45 +0200 Subject: [PATCH 06/25] Note that this is slightly more restrictive at the technical level --- text/0000-rustc-bootstrap.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 666b8d98de2..4d6775bb4fe 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -139,6 +139,7 @@ Cache invalidation should be done by build scripts setting `rerun-if-env-changed [drawbacks]: #drawbacks - This encourages using unstable features on stable, explicitly going against our goals as a project. But people are doing that anyway, and keeping the status quo does not help us prevent them, while causing many other issues. + - Note that while this could be seen as encouragement at the *policy* level, it's actually more restrictive at the technical level, since it requires people to make an exhaustive list of all features they're using. - Rustfmt is often run automatically by editor plugins, not explicitly. Additionally, right now rustfmt warns and continues when a feature gate is enabled on stable, which means the whole codebase gets reformatted. We should make sure rustfmt is changed to instead give a hard error when the environment variable is missing, which will avoid editors accidentally reformatting the whole codebase. [The rustfmt team intends to fix this](https://github.com/rust-lang/rustfmt/issues/5022). - This may make it less likely that people help stabilize features. But stabilizing features is [very very hard](https://medium.com/@ElizAyer/organizational-boundary-problems-too-many-cooks-or-not-enough-kitchens-2ddedc6de26a), and in the meantime people have very little recourse when they need to use an unstable feature. From 8300f6f1dd375802c253f249ee9f57ece2e73272 Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 11:19:36 +0200 Subject: [PATCH 07/25] tmp --- text/0000-rustc-bootstrap.md | 249 ++++++++++++++++++++++++----------- 1 file changed, 171 insertions(+), 78 deletions(-) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 4d6775bb4fe..28d11c5ef5d 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -1,4 +1,4 @@ -- Feature Name: `RUSTC_ALLOW_UNSTABLE` +- Feature Name: `unstable-on-stable` - Start Date: 2025-05-18 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) @@ -6,9 +6,18 @@ # Summary [summary]: #summary -This RFC proposes decoupling the two components of our stability policy: still requiring feature gates, but allowing feature gates to be enabled on stable. It does so by adding `RUSTC_ALLOW_UNSTABLE_` environment variables which can be used to permit using feature gates on stable toolchain channels. These variable are respected by all official Rust tools that use feature gates. +This RFC proposes decoupling the two components of our stability policy: still requiring feature gates, but allowing feature gates to be enabled on stable. -This RFC is *not* intended as a general purpose mechanism for Rust developers to use nightly features on stable; it's specifically targeted at build systems wrapping cargo, such as distro packagers, external tools shipped with the toolchain, and large projects that build a custom Rust toolchain from source. +It does so in three ways: +1. Extending `-Z unstable-options` to take a list of option names, rather than being a simple boolean. +2. Add a new `[workspace.unstable.features]` table to Cargo.toml, allowing Cargo to proxy them through with accurate caching. + `unstable.features` is ignored unless Cargo is passed `--unstable-features`. +3. Add a new `--unstable-flags` flag to Cargo, as well as to all other tools in the toolchain. + `unstable-flags` does not have a feature gate. + +This RFC acknowledges that in practice it will be used as a general purpose mechanism for Rust developers to use nightly features on stable. +However, it's specifically targeted at build systems wrapping cargo, +such as distro packagers, external tools shipped with the toolchain, and large projects that build a custom Rust toolchain from source. # Motivation [motivation]: #motivation @@ -19,32 +28,45 @@ Rust's stability policy has two components: 1. To the extent possible, each unstable feature comes with a feature gate, and is disabled when that feature gate is inactive. [^1] 2. Enabling feature gates is only allowed on the nightly toolchain. -[^1]: There are some exceptions to this, such as https://github.com/rust-lang/rust/issues/139892#issuecomment-2808505610. But in general we attempt to make sure all unstable features have a feature gate. +[^1]: There are some exceptions to this, such as https://github.com/rust-lang/rust/issues/139892#issuecomment-2808505610. + But in general we attempt to make sure all unstable features have a feature gate. -Our motivation for 1 (having feature gates) is to make sure that people do not unknowingly rely on unstable features. This was a big problem for e.g. intra-doc links, which [people often used without knowing they were unstable][63305], making it impossible to remove the feature. +Our motivation for 1 (having feature gates) is to make sure that people do not unknowingly rely on unstable features. +This was a big problem for e.g. intra-doc links, which [people often used without knowing they were unstable][63305], making it impossible to remove the feature. [63305]: https://github.com/rust-lang/rust/issues/63305 Our motivation for 2 (disabling feature gates on stable) is three-fold: 1. Prevent people from relying on features that may change in the future while on the stable toolchain, upholding our "stability without stagnation" motto. -2. Disallow library authors from "silently" opt-ing in to unstable features, such that the person running the top-level build doesn't know they're using unstable features that may break when the toolchain is updated. This rationale doesn't apply to nightly, where the party running the top-level build is assumed to know that nightly comes with no stability guarantees. +2. Disallow library authors from "silently" opt-ing in to unstable features, + such that the person running the top-level build doesn't know they're using unstable features that may break when the toolchain is updated. + This rationale doesn't apply to nightly, where the party running the top-level build is assumed to know that nightly comes with no stability guarantees. 3. Encourage people to help stabilize the features they care about. -There are some cases in which none of those goals are applicable, but we still prevent people from using nightly features. This is particularly bad when projects *must* depend on unstable features to ship another feature they care about. Some examples: -- rust-analyzer and RustRover need `./some-libtest-binary --format=json` to determine the list of possible tests to run -- rust-analyzer and RustRover need all values in `rustc --print=cfg` to build the standard library (see [#139892](https://github.com/rust-lang/rust/issues/139892#issuecomment-2808505610) for an explanation of why this is affected by unstable features) -- `cargo semver-checks` needs `rustdoc --output-format=json` in order to work at all -- Rust for Linux needs a way to build a custom version of core. In particular, they mentioned they need to disable float support, because using float registers can cause unsoundness. +There are some cases in which none of those goals are applicable, but we still prevent people from using nightly features. +This is particularly bad when projects *must* depend on unstable features to ship another feature they care about. +Some examples: +- rust-analyzer and RustRover need `./some-libtest-binary --format=json` to determine the list of possible tests to run. +- rust-analyzer and RustRover need all values in `rustc --print=cfg` to build the standard library. + (see [#139892](https://github.com/rust-lang/rust/issues/139892#issuecomment-2808505610) for an explanation of why this is affected by unstable features) +- `cargo semver-checks` needs `rustdoc --output-format=json` in order to work at all. +- Rust for Linux needs a way to build a custom version of core. + In particular, they mentioned they need to disable float support, because using float registers can cause unsoundness. - `rustc_public`'s entire mission is to wrap unstable APIs with stable ones and therefore needs access to all `rustc_private` features. Why are these uses ok? Two reasons: -- Each of these tools accept responsibility for breakage. `semver-checks` and RfL both explicitly adapt to each new release of rustc, and their feedback on breakage is very useful for improving the features they use. rust-analyzer and RustRover don't break at all for `--print=cfg`—they're not using it in code, only in the CLI—and adapt to any changes in libtest json format. -- These tools act as a "buffer" between other projects and breakage. For example, semver-checks hides the breaking changes behind its own interface such that downstream projects are not affected. Similar, Rust for Linux backports breakage fixes to stable branches such that old versions of the kernel keep building with new rust toolchains. +- Each of these tools accept responsibility for breakage. + `semver-checks` and RfL both explicitly adapt to each new release of rustc, and their feedback on breakage is very useful for improving the features they use. + rust-analyzer and RustRover don't break at all for `--print=cfg`—they're not using it in code, only in the CLI—and adapt to any changes in libtest json format. +- These tools act as a "buffer" between other projects and breakage. + For example, semver-checks hides the breaking changes behind its own interface such that downstream projects are not affected. + Similar, Rust for Linux backports breakage fixes to stable branches such that old versions of the kernel keep building with new rust toolchains. One might ask, well, maybe we are being too eager to gate things, but can't people just use nightly? There are some cases where switching to nightly is not realistic. - When using rustc packaged by a distro (e.g. Fedora or `nixpkgs`), only the stable channel is packaged. - Tools that wrap the compiler (e.g. `rust-analyzer` or `cargo expand`) or libraries (e.g. `proc-macro2`) usually do not control the toolchain version being used. -- Stable with `RUSTC_BOOTSTRAP` is not the same as nightly. In particular, stable contains backports and nightly does not. +- Stable with `RUSTC_BOOTSTRAP` is not the same as nightly. + In particular, stable contains backports and nightly does not. ## Why this exact mechanism? @@ -53,105 +75,172 @@ Currently, these tools use [`RUSTC_BOOTSTRAP=1`][rustc-bootstrap] as a workaroun [rustc-bootstrap]: https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/rustc-bootstrap.html - Enabling RUSTC_BOOTSTRAP for one part of the toolchain enables it for *all* parts of the toolchain; in particular: - - `proc-macro2` uses `cargo:rerun-on-env-changed=RUSTC_BOOTSTRAP`, causing cache thrashing whenever this env var changes. - - rust-analyzer wants to enable RUSTC_BOOTSTRAP only for cargo and libtest, but the variable enables features for rustc as well. `RUSTFLAGS="-Z allow-features="` fixes this for lang features, but at the price of thrashing the cache; and there is no equivalent way to disable unstable CLI features. -- Libraries that detect RUSTC_BOOTSTRAP sometimes do it incorrectly (in particular, `-Zallow-features` often messes things up). To do this correctly, one must compile a full rust program that uses the api the library wants to enable; but in practice doing this is rare. Using more specific environment variables makes it less likely that a single misbehaving library can break the whole build. + - `proc-macro2` uses `cargo:rerun-on-env-changed=RUSTC_BOOTSTRAP`, causing cache thrashing whenever this env var changes. + - rust-analyzer wants to enable RUSTC_BOOTSTRAP only for cargo and libtest, but the variable enables features for rustc as well. + `RUSTFLAGS="-Z allow-features="` fixes this for lang features, but at the price of thrashing the cache; and there is no equivalent way to disable unstable CLI features. +- Libraries that detect RUSTC_BOOTSTRAP sometimes do it incorrectly (in particular, `-Zallow-features` often messes things up). + To do this correctly, one must compile a full rust program that uses the api the library wants to enable; but in practice doing this is rare. + Limiting the opt-in to a specific feature makes it less likely that a single misbehaving library can break the whole build. -An important design constraint here is that the "end-user" (whoever is running the build) should always have control over which features are enabled. To the extent that tools act as a "buffer" between feature breakage and the end-user, they should only take responsibility for exactly the features whose breakage they know how to handle. +An important design constraint here is that the "end-user" (whoever is running the build) should always have control over which features are enabled. +To the extent that tools act as a "buffer" between feature breakage and the end-user, they should only take responsibility for exactly the features whose breakage they know how to handle. -We continue to use environment variables because that only permits the top-level build to allow feature gates. Cargo has `[env]` blocks, but they are only enabled for the top-level build, and it's usually easy for wrapping build systems to ignore `.cargo/config.toml` files. As a side effect, `[env]` makes it easy for developers to experiment locally with nightly features on a stable toolchain, but without allowing them to opt-in silently when their library is published to crates.io. This seems good, actually. +`[workspace.unstable]` only permits the top-level build to allow feature gates. +Build scripts cannot modify Cargo.toml files, and Cargo only respects `[workspace]` for the root manifest. +This allows developers to experiment locally with nightly features on a stable toolchain, but without allowing them to opt-in silently when their library is published to crates.io. +`[workspace.unstable]` # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -The following documentation will live in the [unstable book], not in the [rustc book]. +The following documentation will live in the [unstable book] (or Cargo's [unstable features][cargo-unstable] section), not in the [rustc book]. [unstable book]: https://doc.rust-lang.org/nightly/unstable-book/ +[cargo-unstable]: https://doc.rust-lang.org/cargo/reference/unstable.html#allow-features [rustc book]: https://doc.rust-lang.org/nightly/rustc/ -The `RUSTC_ALLOW_UNSTABLE_*` environment variables allow you to use unstable features on the stable and beta channels. In particular, it allows you to use `-Z` flags and `#![feature(..)]` attributes. - -**NOTE:** This was previously done using a single `RUSTC_BOOTSTRAP` environment variable. **Please** avoid using `RUSTC_BOOTSTRAP`; it causes the Rust Project maintainers many issues. These variables are designed to replace all places where you might need it. - -Each variable is named after the feature or CLI flag it enables. Tools are pseudo-namespaced. For example, `RUSTC_ALLOW_UNSTABLE_VALIDATE_MIR=1` allows using `rustc -Z validate-mir`, `RUSTC_ALLOW_UNSTABLE_ASM_UNWIND` allows using `#![feature(asm_unwind)]`, and `RUSTC_ALLOW_UNSTABLE_RUSTDOC_OUTPUT_FORMAT` allows using `rustdoc --output-format=json`. - -Each variable takes one of three arguments: -1. `1` indicates that all crates can use the feature. This is the default on nightly. -2. `-1` indicates that no crates can use the feature. This is the default on stable and beta, but can be useful when using a nightly toolchain. -3. `ident` indicates that only the crate named `ident` can use the feature. Multiple crates can be specified at once by separating them with commas; for example, `RUSTC_ALLOW_UNSTABLE_ASM_UNWIND=tokio,hyper` allows specifying `#![feature(asm_unwind)]` in the `tokio` and `hyper` crates. - -Note that on most platforms, it is impossible to set an environment variable more than once, so be careful not to overwrite any existing variable. +## For Cargo users + +The `[workspace.unstable.flags]` and `[workspace.unstable.features]` tables allow you to use unstable features on the stable and beta channels. +In particular, they allow you to use `-Z` flags and `#![feature(..)]` attributes, respectively. +These can *only* be used in a local build of your workspace; they cannot be published to crates.io, nor used in a git/path dependency. +Cargo will warn you each time you use these features, as a reminder that they are not stable and may break in the future. + +**NOTE:** This was previously done using a single `RUSTC_BOOTSTRAP` environment variable. +**Please** avoid using `RUSTC_BOOTSTRAP`; it causes the Rust Project maintainers many issues. +These `[workspace.unstable]` features are designed to replace all places where you might need it. + +### Enabling features + +Each option in the `flags` or `features` table is named after the corresponding flag or feature it enables. +For example, the following config says "enable `-Z allow-moves` and `#![feature(allocator_internals)]`, but not any other feature": +```toml +[workspace.unstable.flags] +allow-moves = true +[workspace.unstable.features] +allocator_internals = true +``` +That enables unstable features for all packages in your dependency tree. +You may wish to only enable them for packages in your workspace: +```toml +allow-moves = { workspace = true } +``` +or for specific packages in your dependency tree: +```toml +allow-moves = ["cfg-if"] +``` + +### Disabling features + +So far, we've been assuming that you have a stable toolchain, which disallows all features by default. +But you might also use a nightly toolchain, which allows all features by default. +If so, you might wish to ban all features for all packages. +You can do that by having empty `unstable` tables: +```toml +[workspace.unstable.flags] +[workspace.unstable.features] +# end of TOML file +``` +Note that there is no way to change a stable toolchain to allow features by default. + +## For alternate build systems, or Cargo implementors + +The `--unstable-flags` and `--unstable-features` flags allow you to control precisely which unstable flags/features are used by a given crate. +They are supported by rustc, and by all tools shipped with the official toolchain. + +Each flag takes one of the following strings as values: +- An empty string, which indicates that no flags/features are allowed (default on stable) +- A comma-separated list of flags/features names. + +These flags are specific to the current crate; you can pass different values to different crates and they will interoperate, similar to the `--edition` flag. +This should not be construed to imply that a library crate can opt-in once for the whole build; each crate must opt-in to each feature it uses, or it will get a stability error. + +Flags are named after the feature or CLI flag they enable. +By implication, this means that features use underscores (`_`) and CLI flags use dashes (`-`). + +Unrecognized flags/features are ignored. +As a quality-of-implementation concern, the tool should warn if an unrecognized flag/feature is passed. ## Build scripts -Setting these is not allowed in build scripts and will cause the build to fail. +Build scripts have no mechanism for setting rustc flags (other than `-l` and `-L`) and so cannot use these mechanisms. +This is a feature, not a bug. -Reading these variables and using them to do feature detection is allowed, but strongly discouraged. -Be sure to read the variables for *all* relevant features; respect `-Z allow-features`; and compile a real crate to make sure that your expected usage matches up with the version of the feature that's implemented. -Set `cargo::rerun-if-env-changed` for all feature gates that could possibly be enabled by your build script. +Reading these variables from `CARGO_ENCODED_RUSTFLAGS` and using them to do feature detection is allowed, but strongly discouraged. +Be sure to read both `unstable-flags` and `unstable-features` and **compile a real crate** to make sure that your expected usage matches up with the version of the feature that's implemented. Do *not* simply check whether this is a nightly compiler or not. ## Stability policy -Despite being usable on stable, this is an unstable feature. Like any other unstable feature, we reserve the right to change or remove this feature in the future, as well as any other unstable feature that it enables. Using this feature is opting out of the normal stability/backwards compatibility guarantee of stable. +Despite being usable on stable, this is an unstable feature. +Like any other unstable feature, we reserve the right to change or remove this feature in the future, as well as any other unstable feature that it enables. +Using this feature is opting out of the normal stability/backwards compatibility guarantee of stable. -Although we do not take technical measures to prevent it from being used, we strongly discourage using this feature. If at all possible, please contribute to stabilizing the features you care about instead of bypassing the Rust project's stability policy. +Although we do not take technical measures to prevent it from being used, we strongly discourage using this feature. +If at all possible, please contribute to stabilizing the features you care about instead of bypassing the Rust project's stability policy. -If you do use this to enable an unstable feature, please contact a member of the project who works on the feature in question, so that we know who is exposed to breakage. For example, if you are using `RUSTC_ALLOW_UNSTABLE_RUSTDOC_OUTPUT_FORMAT=1`, reach out to [Alona Enraght-Moony][alona] (the maintainer of rustdoc-json). If you do not know who to contact, ask on [Zulip]. Contacting a maintainer provides no stability guarantees and does not mean the maintainer will agree to work with you, but can help us find a alternative solution to your problem or otherwise improve the unstable feature you are using. +If you do use this to enable an unstable feature, please contact a member of the project who works on the feature in question, so that we know who is exposed to breakage. +For example, if you are using `rustdoc --unstable-flags=output-format`, reach out to [Alona Enraght-Moony][alona] (the maintainer of rustdoc-json). +If you do not know who to contact, ask on [Zulip]. +Contacting a maintainer provides no stability guarantees and does not mean the maintainer will agree to work with you, +but can help us find a alternative solution to your problem or otherwise improve the unstable feature you are using. [alona]: https://github.com/aDotInTheVoid/ [Zulip]: https://rust-lang.zulipchat.com/ # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -## Determining variable names - -Each environment variable name is determined as follows: -1. Start with the prefix `RUSTC_ALLOW_UNSTABLE_` -2. Add the name of the tool in uppercase followed by an underscore, if present. For example, clippy would append `CLIPPY_`. For the purpose of this RFC, any generated libtest binaries are counted as a tool and append `TEST_`. -3. Append the feature name. - a. For language and library features, append the name of the feature gate in uppercase. For example, `#![feature(asm_unwind)]` would append `ASM_UNWIND`. - b. For cli flags, append the name of the flag in uppercase, excluding any `-Z` prefix, and replacing dashes with underscores. For example, `-Z validate-mir` would append `VALIDATE_MIR`. - -As a quality-of-implementation concern, the tool may warn when a `crate_name` passed to an environment variable is not a valid Rust identifier (this may happen if, e.g., a cargo package name is used instead of the proper crate name). - -As a quality-of-implementation concern, the tool should warn when an unrecognized feature is permitted. +## Caching -As a quality-of-implementation concern, the compiler should verify (i.e. through testing, not at runtime) that no CLI flag name would cause its environment variable to overlap with a feature gate. -Existing feature gates that cause such a conflict should be renamed. -For example, `-Z ub-checks` and `feature(ub_checks)` cause an overlap under this proposal; `feature(ub_checks)` should be renamed to avoid the overlap. -Note that this is only relevant to the compiler, since other tools are already pseudo-namespaced and can't have conflicts. +Currently, changing `RUSTC_BOOTSTRAP` does not invalidate Cargo's build cache. +With this flag, Cargo will know exactly which crates are affected by each flag, +and can choose to rebuild only the crates it needs to. -As a quality-of-implementation concern, the compiler should verify (through testing) that there are no conflicts between a compiler feature and an official tool feature; for example, it should verify that `feature(rustdoc_internals)` does not conflict with a rustdoc flag named `-Z internals`. +Build scripts do not need to explicitly tell Cargo when they are rebuilt; +Cargo should rerun them when it rebuilds their package. -## Restricting variables in build scripts +We suggest, but do not require, that flag is made part of the fingerprint tracking, not unit cache tracking, +so that changing the enabled features overwrites the cache rather than adding to it. -Currently, Cargo restricts setting `cargo::rustc-env=RUSTC_BOOSTRAP=1` from a build script. -Once this RFC is accepted, it will also restrict any variable starting with `RUSTC_ALLOW_UNSTABLE_`. This does not require coordination between the compiler and Cargo and so I do not expect it to be a high maintenance burden. +## Existing flags -## Caching - -Currently, changing `RUSTC_BOOTSTRAP` does not invalidate Cargo's build cache. -We suggest keeping this state of affairs so that setting a single variable does not require rebuilding the whole dependency tree; the whole point of not using `RUSTFLAGS` is to avoid rebuilding unnecessarily. -Cache invalidation should be done by build scripts setting `rerun-if-env-changed` if necessary. +The existing `-Z allow-features` and `-Z unstable-options` flags in Rustc/tools will be removed. +`cargo -Z allow-features` will remain, but it will only apply to Cargo itself, not to any invoked tools. # Drawbacks [drawbacks]: #drawbacks -- This encourages using unstable features on stable, explicitly going against our goals as a project. But people are doing that anyway, and keeping the status quo does not help us prevent them, while causing many other issues. - - Note that while this could be seen as encouragement at the *policy* level, it's actually more restrictive at the technical level, since it requires people to make an exhaustive list of all features they're using. -- Rustfmt is often run automatically by editor plugins, not explicitly. Additionally, right now rustfmt warns and continues when a feature gate is enabled on stable, which means the whole codebase gets reformatted. We should make sure rustfmt is changed to instead give a hard error when the environment variable is missing, which will avoid editors accidentally reformatting the whole codebase. [The rustfmt team intends to fix this](https://github.com/rust-lang/rustfmt/issues/5022). -- This may make it less likely that people help stabilize features. But stabilizing features is [very very hard](https://medium.com/@ElizAyer/organizational-boundary-problems-too-many-cooks-or-not-enough-kitchens-2ddedc6de26a), and in the meantime people have very little recourse when they need to use an unstable feature. +- This encourages using unstable features on stable, explicitly going against our goals as a project. + But people are doing that anyway, and keeping the status quo does not help us prevent them, while causing many other issues. + - Note that while this could be seen as encouragement at the *policy* level, it's actually more restrictive at the *technical* level, + since it requires people to make an exhaustive list of all features they're using. +- Rustfmt is often run automatically by editor plugins, not explicitly. + Additionally, right now rustfmt warns and continues when a feature gate is enabled on stable, which means the whole codebase gets reformatted. + We should make sure rustfmt is changed to instead give a hard error when the feature gate is disabled, which will avoid editors accidentally reformatting the whole codebase. + [The rustfmt team intends to fix this](https://github.com/rust-lang/rustfmt/issues/5022). +- This may make it less likely that people help stabilize features. + But stabilizing features is [very very hard](https://medium.com/@ElizAyer/organizational-boundary-problems-too-many-cooks-or-not-enough-kitchens-2ddedc6de26a), + and in the meantime people have very little recourse when they need to use an unstable feature. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -- We could allow specifying individual values of a CLI flag, not just the name of the value. For example, this could be useful for libtest's `--format` flag, to only allow `--format=json` but not `--format=junit`. I think in practice it will not cause issues to lump these together. We always have the option to add more variables in the future; and because this whole feature is perma-unstable, we have the option to rename existing vars as well. -- We could have a single environment variable which takes the feature name as a value. That does not allow composing the variables - setting it in one place breaks all other places that set it. Additionally, it does not allow build scripts to do fine-grained caching, because cargo only has `rerun-on-env-changed` and not anything more granular. -- We could use CLI flags instead of an environment variable. That breaks caching, because rust-analyzer cannot pass `RUSTFLAGS=-Zallow-features` without cargo rebuilding, and the cargo team does not wish to inspect the contents of RUSTFLAGS. -- We can """simply""" tell people to stop using nightly features on stable (either politely, or with technical measures). This will have a large negative impact on the ecosystem - rust-analyzer and RustRover will not support running unit tests on stable; `cargo semver-checks` will not work at all on stable; Rust for Linux will break entirely. -- We can leave the status quo. This is in many ways the worst of all worlds - people still use unstable features on stable, but in hacky ways that break. -- We can separate the toolchain into "stable" and "unstable" channels, and tell distros to package both. This is a big ask from distros, and does not actually help with many of the problems (for example cargo semver-checks cannot rely on it being installed, and rust-analyzer will still have caching issues). +- We could allow specifying individual values of a CLI flag, not just the name of the value. + For example, this could be useful for libtest's `--format` flag, to only allow `--format=json` but not `--format=junit`. + I think in practice it will not cause issues to lump these together. + We always have the option to extend the syntax in the future; and because this whole feature is perma-unstable, we have the option to rename existing flags as well. +- We could use environment variables instead of flags. + This requires no coordination with Cargo and makes the feature seem less "official", which might discourage people from using it. + But it makes the caching situation much worse, and runs into platform-specific limitations like not being able to set an env var more than one time, or hitting implementation limits on the number of env vars that can be set. + Flags avoid this by using [response files] and allowing allow-features to be additive. +- We can """simply""" tell people to stop using nightly features on stable (either politely, or with technical measures). + This will have a large negative impact on the ecosystem - + rust-analyzer and RustRover will not support running unit tests on stable; `cargo semver-checks` will not work at all on stable; Rust for Linux will break entirely. +- We can leave the status quo. + This is in many ways the worst of all worlds - people still use unstable features on stable, but in hacky ways that break. +- We can separate the toolchain into "stable" and "unstable" channels, and tell distros to package both. + This is a big ask from distros, and does not actually help with many of the problems + (for example cargo semver-checks cannot rely on it being installed, and rust-analyzer will still have caching issues). This cannot be done in a library or macro. # Prior art @@ -174,9 +263,9 @@ This cannot be done in a library or macro. # Unresolved questions [unresolved-questions]: #unresolved-questions -- Will this run into platform-specific limitations on [env variable lengths][limits.h] when many env variables are passed? The "minimum maximum" is 4096 but in practice most platforms seem to be much higher. Unlike flags, environment variables cannot be passed in [response files]. -[limits.h]: https://pubs.opengroup.org/onlinepubs/009695399/basedefs/limits.h.html#:~:text=ARG_MAX +None to my knowledge. + [response files]: https://doc.rust-lang.org/rustc/command-line-arguments.html#path-load-command-line-flags-from-a-path # Future possibilities @@ -184,5 +273,9 @@ This cannot be done in a library or macro. - Break hard on RUSTC_BOOTSTRAP now that people have an alternative. For example, we could remove the `RUSTC_BOOTSTRAP=crate_name` syntax and instead require `RUSTC_BOOTSTRAP=` of the commit rustc was built with. The goal here is for no one to use the variable except bootstrap itself. - Rename RUSTC_BOOTSTRAP to a name that makes more sense, such as `RUSTC_ALLOW_ALL_FEATURES`. -- We could split unstable features into "alpha" and "beta", and only allow the latter to be enabled with `RUSTC_ALLOW_UNSTABLE`. Additionally, we could enable beta features by default on the beta channel. -- We could add a version scheme to unstable features, such that the opt-in has to specify exactly which version of the feature it expects (and gets a hard error if its expected version doesn't match the version implemented in the compiler). The syntax for the opt-in would look like `RUSTC_ALLOW_UNSTABLE_NAME=2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. To encourage contributors to bump the version, we could remind them (e.g. in a Github comment when a PR is opened) whenever a test that uses the feature is modified. +- We could split unstable features into "alpha" and "beta", and only allow the latter to be enabled with `--unstable-features`. + Additionally, we could enable beta features by default on the beta channel. +- We could add a version scheme to unstable features, such that the opt-in has to specify exactly which version of the feature it expects + (and gets a hard error if its expected version doesn't match the version implemented in the compiler). + The syntax for the opt-in would look like `--unstable-features=allow-moves=2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. + To encourage project contributors to bump the version, we could remind them (e.g. in a Github comment when a PR is opened) whenever a test that uses the feature is modified. From 15df5d580ebfd90c61baf95108d51d3e7577d5c7 Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 13:39:06 +0200 Subject: [PATCH 08/25] refactor rfc to be about unstable flags --- text/0000-rustc-bootstrap.md | 148 +++++++++++------------------------ 1 file changed, 47 insertions(+), 101 deletions(-) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 28d11c5ef5d..f5be26ef98c 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -6,18 +6,17 @@ # Summary [summary]: #summary -This RFC proposes decoupling the two components of our stability policy: still requiring feature gates, but allowing feature gates to be enabled on stable. +This RFC proposes decoupling the two components of our stability policy, for CLI flags only: +still requiring feature gates, but allowing feature gates to be enabled on stable. -It does so in three ways: +It does so in two ways: 1. Extending `-Z unstable-options` to take a list of option names, rather than being a simple boolean. -2. Add a new `[workspace.unstable.features]` table to Cargo.toml, allowing Cargo to proxy them through with accurate caching. - `unstable.features` is ignored unless Cargo is passed `--unstable-features`. -3. Add a new `--unstable-flags` flag to Cargo, as well as to all other tools in the toolchain. - `unstable-flags` does not have a feature gate. + Then, renaming it to `--unstable-flags`. `unstable-flags` does not have a feature gate. +2. Add a new `--unstable-flags` flag to Cargo, which propagates it to all invoked commands with proper caching. -This RFC acknowledges that in practice it will be used as a general purpose mechanism for Rust developers to use nightly features on stable. -However, it's specifically targeted at build systems wrapping cargo, -such as distro packagers, external tools shipped with the toolchain, and large projects that build a custom Rust toolchain from source. +This RFC is *not* intended as a general purpose mechanism for Rust developers to use nightly features on stable; +it's specifically targeted at build systems wrapping cargo, such as distro packagers, external tools shipped with the toolchain, and large projects that build a custom Rust toolchain from source. +As such, it does not attempt to address unstable lang features, which we consider adequately addressed by `RUSTC_BOOTSTRAP=crate_name`. # Motivation [motivation]: #motivation @@ -55,6 +54,8 @@ Some examples: - `rustc_public`'s entire mission is to wrap unstable APIs with stable ones and therefore needs access to all `rustc_private` features. Why are these uses ok? Two reasons: +- Each of these, except for rustc_public, is an external tool, not a library. + They do not need unstable language features, only unstable tool output. - Each of these tools accept responsibility for breakage. `semver-checks` and RfL both explicitly adapt to each new release of rustc, and their feedback on breakage is very useful for improving the features they use. rust-analyzer and RustRover don't break at all for `--print=cfg`—they're not using it in code, only in the CLI—and adapt to any changes in libtest json format. @@ -62,7 +63,8 @@ Why are these uses ok? Two reasons: For example, semver-checks hides the breaking changes behind its own interface such that downstream projects are not affected. Similar, Rust for Linux backports breakage fixes to stable branches such that old versions of the kernel keep building with new rust toolchains. -One might ask, well, maybe we are being too eager to gate things, but can't people just use nightly? There are some cases where switching to nightly is not realistic. +One might ask, well, maybe we are being too eager to gate things, but can't people just use nightly? +There are some cases where switching to nightly is not realistic. - When using rustc packaged by a distro (e.g. Fedora or `nixpkgs`), only the stable channel is packaged. - Tools that wrap the compiler (e.g. `rust-analyzer` or `cargo expand`) or libraries (e.g. `proc-macro2`) usually do not control the toolchain version being used. - Stable with `RUSTC_BOOTSTRAP` is not the same as nightly. @@ -74,21 +76,18 @@ Currently, these tools use [`RUSTC_BOOTSTRAP=1`][rustc-bootstrap] as a workaroun [rustc-bootstrap]: https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/rustc-bootstrap.html -- Enabling RUSTC_BOOTSTRAP for one part of the toolchain enables it for *all* parts of the toolchain; in particular: - - `proc-macro2` uses `cargo:rerun-on-env-changed=RUSTC_BOOTSTRAP`, causing cache thrashing whenever this env var changes. - - rust-analyzer wants to enable RUSTC_BOOTSTRAP only for cargo and libtest, but the variable enables features for rustc as well. +Enabling RUSTC_BOOTSTRAP for one part of the toolchain enables it for *all* parts of the toolchain; in particular: + - `proc-macro2` uses `cargo:rerun-on-env-changed=RUSTC_BOOTSTRAP`, causing cache thrashing whenever this env var changes. + - rust-analyzer wants to enable RUSTC_BOOTSTRAP only for cargo and libtest, but the variable enables features for rustc as well. `RUSTFLAGS="-Z allow-features="` fixes this for lang features, but at the price of thrashing the cache; and there is no equivalent way to disable unstable CLI features. -- Libraries that detect RUSTC_BOOTSTRAP sometimes do it incorrectly (in particular, `-Zallow-features` often messes things up). - To do this correctly, one must compile a full rust program that uses the api the library wants to enable; but in practice doing this is rare. - Limiting the opt-in to a specific feature makes it less likely that a single misbehaving library can break the whole build. An important design constraint here is that the "end-user" (whoever is running the build) should always have control over which features are enabled. -To the extent that tools act as a "buffer" between feature breakage and the end-user, they should only take responsibility for exactly the features whose breakage they know how to handle. +To the extent that tools act as a "buffer" between feature breakage and the end-user, +they should only take responsibility for exactly the features whose breakage they know how to handle. -`[workspace.unstable]` only permits the top-level build to allow feature gates. -Build scripts cannot modify Cargo.toml files, and Cargo only respects `[workspace]` for the root manifest. -This allows developers to experiment locally with nightly features on a stable toolchain, but without allowing them to opt-in silently when their library is published to crates.io. -`[workspace.unstable]` +We mark language features out of scope, because we expect the new flags to reduce most of the use cases for RUSTC_BOOTSTRAP, and therefore to reduce the amount of needless cache thrashing going on. +We do not want to decouple our stability policy for language features, +because there's no possibility there of the library acting as a buffer between other projects and breakage[^2]. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -99,76 +98,22 @@ The following documentation will live in the [unstable book] (or Cargo's [unstab [cargo-unstable]: https://doc.rust-lang.org/cargo/reference/unstable.html#allow-features [rustc book]: https://doc.rust-lang.org/nightly/rustc/ -## For Cargo users - -The `[workspace.unstable.flags]` and `[workspace.unstable.features]` tables allow you to use unstable features on the stable and beta channels. -In particular, they allow you to use `-Z` flags and `#![feature(..)]` attributes, respectively. -These can *only* be used in a local build of your workspace; they cannot be published to crates.io, nor used in a git/path dependency. -Cargo will warn you each time you use these features, as a reminder that they are not stable and may break in the future. - **NOTE:** This was previously done using a single `RUSTC_BOOTSTRAP` environment variable. **Please** avoid using `RUSTC_BOOTSTRAP`; it causes the Rust Project maintainers many issues. These `[workspace.unstable]` features are designed to replace all places where you might need it. -### Enabling features - -Each option in the `flags` or `features` table is named after the corresponding flag or feature it enables. -For example, the following config says "enable `-Z allow-moves` and `#![feature(allocator_internals)]`, but not any other feature": -```toml -[workspace.unstable.flags] -allow-moves = true -[workspace.unstable.features] -allocator_internals = true -``` -That enables unstable features for all packages in your dependency tree. -You may wish to only enable them for packages in your workspace: -```toml -allow-moves = { workspace = true } -``` -or for specific packages in your dependency tree: -```toml -allow-moves = ["cfg-if"] -``` - -### Disabling features - -So far, we've been assuming that you have a stable toolchain, which disallows all features by default. -But you might also use a nightly toolchain, which allows all features by default. -If so, you might wish to ban all features for all packages. -You can do that by having empty `unstable` tables: -```toml -[workspace.unstable.flags] -[workspace.unstable.features] -# end of TOML file -``` -Note that there is no way to change a stable toolchain to allow features by default. - -## For alternate build systems, or Cargo implementors - -The `--unstable-flags` and `--unstable-features` flags allow you to control precisely which unstable flags/features are used by a given crate. -They are supported by rustc, and by all tools shipped with the official toolchain. +The `--unstable-flags` option allow you to control precisely which unstable options are used by a given crate. +It's supported by rustc, and by all tools shipped with the official toolchain. Each flag takes one of the following strings as values: -- An empty string, which indicates that no flags/features are allowed (default on stable) -- A comma-separated list of flags/features names. - -These flags are specific to the current crate; you can pass different values to different crates and they will interoperate, similar to the `--edition` flag. -This should not be construed to imply that a library crate can opt-in once for the whole build; each crate must opt-in to each feature it uses, or it will get a stability error. - -Flags are named after the feature or CLI flag they enable. -By implication, this means that features use underscores (`_`) and CLI flags use dashes (`-`). - -Unrecognized flags/features are ignored. -As a quality-of-implementation concern, the tool should warn if an unrecognized flag/feature is passed. +- An empty string, which indicates that no flags are allowed (default on stable) +- A comma-separated list of flags names. -## Build scripts +Flags are named after the CLI flag they enable. +By implication, this means that CLI flags use dashes (`-`). -Build scripts have no mechanism for setting rustc flags (other than `-l` and `-L`) and so cannot use these mechanisms. -This is a feature, not a bug. - -Reading these variables from `CARGO_ENCODED_RUSTFLAGS` and using them to do feature detection is allowed, but strongly discouraged. -Be sure to read both `unstable-flags` and `unstable-features` and **compile a real crate** to make sure that your expected usage matches up with the version of the feature that's implemented. -Do *not* simply check whether this is a nightly compiler or not. +Unrecognized flags are ignored. +As a quality-of-implementation concern, the tool should warn if an unrecognized flag is passed. ## Stability policy @@ -192,21 +137,13 @@ but can help us find a alternative solution to your problem or otherwise improve ## Caching -Currently, changing `RUSTC_BOOTSTRAP` does not invalidate Cargo's build cache. -With this flag, Cargo will know exactly which crates are affected by each flag, -and can choose to rebuild only the crates it needs to. - -Build scripts do not need to explicitly tell Cargo when they are rebuilt; -Cargo should rerun them when it rebuilds their package. +Currently, changing `RUSTC_BOOTSTRAP` does not invalidate Cargo's build cache by itself, but in practice can cause build scripts deep in the dependency tree to re-run. +With `--unstable-flags`, we separate the mechanism for enabling flags from the mechanism for enabling lang features, +greatly decreasing how often build scripts run. -We suggest, but do not require, that flag is made part of the fingerprint tracking, not unit cache tracking, +We suggest, but do not require, that the flag is made part of the fingerprint tracking, not unit cache tracking, so that changing the enabled features overwrites the cache rather than adding to it. -## Existing flags - -The existing `-Z allow-features` and `-Z unstable-options` flags in Rustc/tools will be removed. -`cargo -Z allow-features` will remain, but it will only apply to Cargo itself, not to any invoked tools. - # Drawbacks [drawbacks]: #drawbacks @@ -221,6 +158,14 @@ The existing `-Z allow-features` and `-Z unstable-options` flags in Rustc/tools - This may make it less likely that people help stabilize features. But stabilizing features is [very very hard](https://medium.com/@ElizAyer/organizational-boundary-problems-too-many-cooks-or-not-enough-kitchens-2ddedc6de26a), and in the meantime people have very little recourse when they need to use an unstable feature. +- This does not address the use case of lang features. + Lang features have several drawbacks right now; for example: + - It's possible to enable/disable features for individual crates, or for individaul features, but not both at once. + We could address this by stabilizing `-Zallow-features`, but still requiring it to be used in combination with `RUSTC_BOOTSTRAP=crate_name` (and possibly removing the `RUSTC_BOOTSTRAP=1` form). + - Enabling/disabling lang features causes large parts of the build graph to be rebuilt. + I do not have ideas for how to fix this; the closest I got was a `[workspace.unstable]` table in Cargo.toml, + which would only be read with `cargo build --unstable-features` and passed through `-Zallow-features` to Rustc, + but this seems too corrosive to our stability policy to encourage. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -264,18 +209,19 @@ This cannot be done in a library or macro. # Unresolved questions [unresolved-questions]: #unresolved-questions -None to my knowledge. +Will this cause flags to be de-facto stable, even moreso than they are now? [response files]: https://doc.rust-lang.org/rustc/command-line-arguments.html#path-load-command-line-flags-from-a-path # Future possibilities [future-possibilities]: #future-possibilities -- Break hard on RUSTC_BOOTSTRAP now that people have an alternative. For example, we could remove the `RUSTC_BOOTSTRAP=crate_name` syntax and instead require `RUSTC_BOOTSTRAP=` of the commit rustc was built with. The goal here is for no one to use the variable except bootstrap itself. - Rename RUSTC_BOOTSTRAP to a name that makes more sense, such as `RUSTC_ALLOW_ALL_FEATURES`. -- We could split unstable features into "alpha" and "beta", and only allow the latter to be enabled with `--unstable-features`. - Additionally, we could enable beta features by default on the beta channel. -- We could add a version scheme to unstable features, such that the opt-in has to specify exactly which version of the feature it expects +- We could split unstable flags into "alpha" and "beta", and only allow the latter to be enabled with `--unstable-flags`. + Additionally, we could enable beta flags by default on the beta channel. +- We could add a version scheme to unstable flags, such that the opt-in has to specify exactly which version of the feature it expects (and gets a hard error if its expected version doesn't match the version implemented in the compiler). - The syntax for the opt-in would look like `--unstable-features=allow-moves=2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. + The syntax for the opt-in would look like `--unstable-flags=output-format=2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. To encourage project contributors to bump the version, we could remind them (e.g. in a Github comment when a PR is opened) whenever a test that uses the feature is modified. + +[^2]: Theoretically libraries can write a build script that does feature detection, but this slows down the build for everyone, and it's very very hard to write that build script properly. From 4c0fde0d15c79068b6ea8329cfce20bf965c52ee Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 14:09:24 +0200 Subject: [PATCH 09/25] consistency --- text/0000-rustc-bootstrap.md | 61 +++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index f5be26ef98c..1996f8909e9 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -1,4 +1,4 @@ -- Feature Name: `unstable-on-stable` +- Feature Name: `unstable-flags` - Start Date: 2025-05-18 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) @@ -10,8 +10,8 @@ This RFC proposes decoupling the two components of our stability policy, for CLI still requiring feature gates, but allowing feature gates to be enabled on stable. It does so in two ways: -1. Extending `-Z unstable-options` to take a list of option names, rather than being a simple boolean. - Then, renaming it to `--unstable-flags`. `unstable-flags` does not have a feature gate. +1. Extend `-Z unstable-options` to take a list of option names, rather than being a simple boolean. + Then, rename it to `--unstable-flags`. `unstable-flags` is always available, even on stable. 2. Add a new `--unstable-flags` flag to Cargo, which propagates it to all invoked commands with proper caching. This RFC is *not* intended as a general purpose mechanism for Rust developers to use nightly features on stable; @@ -49,11 +49,11 @@ Some examples: - rust-analyzer and RustRover need all values in `rustc --print=cfg` to build the standard library. (see [#139892](https://github.com/rust-lang/rust/issues/139892#issuecomment-2808505610) for an explanation of why this is affected by unstable features) - `cargo semver-checks` needs `rustdoc --output-format=json` in order to work at all. -- Rust for Linux needs a way to build a custom version of core. +- Rust for Linux (RfL) needs a way to build a custom version of core. In particular, they mentioned they need to disable float support, because using float registers can cause unsoundness. - `rustc_public`'s entire mission is to wrap unstable APIs with stable ones and therefore needs access to all `rustc_private` features. -Why are these uses ok? Two reasons: +Why are these uses ok? Three reasons: - Each of these, except for rustc_public, is an external tool, not a library. They do not need unstable language features, only unstable tool output. - Each of these tools accept responsibility for breakage. @@ -61,13 +61,13 @@ Why are these uses ok? Two reasons: rust-analyzer and RustRover don't break at all for `--print=cfg`—they're not using it in code, only in the CLI—and adapt to any changes in libtest json format. - These tools act as a "buffer" between other projects and breakage. For example, semver-checks hides the breaking changes behind its own interface such that downstream projects are not affected. - Similar, Rust for Linux backports breakage fixes to stable branches such that old versions of the kernel keep building with new rust toolchains. + Similar, RfL backports breakage fixes to stable branches such that old versions of the kernel keep building with new rust toolchains. One might ask, well, maybe we are being too eager to gate things, but can't people just use nightly? There are some cases where switching to nightly is not realistic. - When using rustc packaged by a distro (e.g. Fedora or `nixpkgs`), only the stable channel is packaged. - Tools that wrap the compiler (e.g. `rust-analyzer` or `cargo expand`) or libraries (e.g. `proc-macro2`) usually do not control the toolchain version being used. -- Stable with `RUSTC_BOOTSTRAP` is not the same as nightly. +- Nightly is not the same as stable-with-`RUSTC_BOOTSTRAP`. In particular, stable contains backports and nightly does not. ## Why this exact mechanism? @@ -77,9 +77,9 @@ Currently, these tools use [`RUSTC_BOOTSTRAP=1`][rustc-bootstrap] as a workaroun [rustc-bootstrap]: https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/rustc-bootstrap.html Enabling RUSTC_BOOTSTRAP for one part of the toolchain enables it for *all* parts of the toolchain; in particular: - - `proc-macro2` uses `cargo:rerun-on-env-changed=RUSTC_BOOTSTRAP`, causing cache thrashing whenever this env var changes. - - rust-analyzer wants to enable RUSTC_BOOTSTRAP only for cargo and libtest, but the variable enables features for rustc as well. - `RUSTFLAGS="-Z allow-features="` fixes this for lang features, but at the price of thrashing the cache; and there is no equivalent way to disable unstable CLI features. +- `proc-macro2` uses `cargo:rerun-on-env-changed=RUSTC_BOOTSTRAP`, causing cache thrashing whenever this env var changes. +- rust-analyzer wants to enable RUSTC_BOOTSTRAP only for cargo and libtest, but the variable enables features for rustc as well. + `RUSTFLAGS="-Z allow-features="` fixes this for lang features, but at the price of thrashing the cache; and there is no equivalent way to disable unstable CLI features. An important design constraint here is that the "end-user" (whoever is running the build) should always have control over which features are enabled. To the extent that tools act as a "buffer" between feature breakage and the end-user, @@ -89,6 +89,8 @@ We mark language features out of scope, because we expect the new flags to reduc We do not want to decouple our stability policy for language features, because there's no possibility there of the library acting as a buffer between other projects and breakage[^2]. +[^2]: Theoretically libraries can write a build script that does feature detection, but this slows down the build for everyone, and it's very very hard to write that build script properly. + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -98,23 +100,16 @@ The following documentation will live in the [unstable book] (or Cargo's [unstab [cargo-unstable]: https://doc.rust-lang.org/cargo/reference/unstable.html#allow-features [rustc book]: https://doc.rust-lang.org/nightly/rustc/ -**NOTE:** This was previously done using a single `RUSTC_BOOTSTRAP` environment variable. -**Please** avoid using `RUSTC_BOOTSTRAP`; it causes the Rust Project maintainers many issues. -These `[workspace.unstable]` features are designed to replace all places where you might need it. - -The `--unstable-flags` option allow you to control precisely which unstable options are used by a given crate. +The `--unstable-flags` option allows you to control precisely which unstable options are used by a given crate. It's supported by rustc, and by all tools shipped with the official toolchain. -Each flag takes one of the following strings as values: +Each flag takes one of the following strings as a value: - An empty string, which indicates that no flags are allowed (default on stable) -- A comma-separated list of flags names. +- A comma-separated list of flag names. Flags are named after the CLI flag they enable. By implication, this means that CLI flags use dashes (`-`). -Unrecognized flags are ignored. -As a quality-of-implementation concern, the tool should warn if an unrecognized flag is passed. - ## Stability policy Despite being usable on stable, this is an unstable feature. @@ -128,14 +123,31 @@ If you do use this to enable an unstable feature, please contact a member of the For example, if you are using `rustdoc --unstable-flags=output-format`, reach out to [Alona Enraght-Moony][alona] (the maintainer of rustdoc-json). If you do not know who to contact, ask on [Zulip]. Contacting a maintainer provides no stability guarantees and does not mean the maintainer will agree to work with you, -but can help us find a alternative solution to your problem or otherwise improve the unstable feature you are using. +but can help us find an alternative solution to your problem or otherwise improve the unstable feature you are using. [alona]: https://github.com/aDotInTheVoid/ [Zulip]: https://rust-lang.zulipchat.com/ + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -## Caching +`unstable-flags` is a comma-separated list of CLI flag names. +Flag values are not supported, only names. + +`cargo --unstable-flags` applies to both Cargo and all tools it spawns. +That is, it passes the flag to Rustc when Rustc is spawned. +This applies to all packages, not just the current workspace. +In practice, the only tool passing unstable flags to Cargo is `cargo-semver-checks`, which is building all dependencies in any case. + +If `unstable-flags` is passed multiple times, the *intersection* of all values is used, not the union. +This matters in cases where two parties don't trust each other, such as running `cargo build --unstable-flags=x` in a workspace with `build.rustflags=--unstable-flags=x,y`. + +Unrecognized flag names in `unstable-flags` are ignored. +As a quality-of-implementation concern, the tool should warn if an unrecognized flag is passed. + +## Implementation notes + +### Caching Currently, changing `RUSTC_BOOTSTRAP` does not invalidate Cargo's build cache by itself, but in practice can cause build scripts deep in the dependency tree to re-run. With `--unstable-flags`, we separate the mechanism for enabling flags from the mechanism for enabling lang features, @@ -160,7 +172,7 @@ so that changing the enabled features overwrites the cache rather than adding to and in the meantime people have very little recourse when they need to use an unstable feature. - This does not address the use case of lang features. Lang features have several drawbacks right now; for example: - - It's possible to enable/disable features for individual crates, or for individaul features, but not both at once. + - It's possible to enable/disable features for individual crates, or for individual features, but not both at once. We could address this by stabilizing `-Zallow-features`, but still requiring it to be used in combination with `RUSTC_BOOTSTRAP=crate_name` (and possibly removing the `RUSTC_BOOTSTRAP=1` form). - Enabling/disabling lang features causes large parts of the build graph to be rebuilt. I do not have ideas for how to fix this; the closest I got was a `[workspace.unstable]` table in Cargo.toml, @@ -188,6 +200,7 @@ so that changing the enabled features overwrites the cache rather than adding to (for example cargo semver-checks cannot rely on it being installed, and rust-analyzer will still have caching issues). This cannot be done in a library or macro. + # Prior art [prior-art]: #prior-art @@ -223,5 +236,3 @@ Will this cause flags to be de-facto stable, even moreso than they are now? (and gets a hard error if its expected version doesn't match the version implemented in the compiler). The syntax for the opt-in would look like `--unstable-flags=output-format=2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. To encourage project contributors to bump the version, we could remind them (e.g. in a Github comment when a PR is opened) whenever a test that uses the feature is modified. - -[^2]: Theoretically libraries can write a build script that does feature detection, but this slows down the build for everyone, and it's very very hard to write that build script properly. From 2865f587bc500cba7e3b87925531ade14cc7bcba Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 14:26:09 +0200 Subject: [PATCH 10/25] revise --- text/0000-rustc-bootstrap.md | 57 +++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 1996f8909e9..dd2ef198290 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -13,6 +13,7 @@ It does so in two ways: 1. Extend `-Z unstable-options` to take a list of option names, rather than being a simple boolean. Then, rename it to `--unstable-flags`. `unstable-flags` is always available, even on stable. 2. Add a new `--unstable-flags` flag to Cargo, which propagates it to all invoked commands with proper caching. +3. Change Rustfmt to give a hard error if a feature gate is disabled. This RFC is *not* intended as a general purpose mechanism for Rust developers to use nightly features on stable; it's specifically targeted at build systems wrapping cargo, such as distro packagers, external tools shipped with the toolchain, and large projects that build a custom Rust toolchain from source. @@ -91,6 +92,13 @@ because there's no possibility there of the library acting as a buffer between o [^2]: Theoretically libraries can write a build script that does feature detection, but this slows down the build for everyone, and it's very very hard to write that build script properly. +## Why change Rustfmt? + +Rustfmt is often run automatically by editor plugins, not explicitly. +Additionally, right now rustfmt warns and continues when a feature gate is enabled on stable, which means the whole codebase gets reformatted. +Changing Rustfmt to instead give a hard error when the feature gate is disabled avoids editors accidentally reformatting the whole codebase. +[The rustfmt team already intends to fix this](https://github.com/rust-lang/rustfmt/issues/5022). + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -100,15 +108,16 @@ The following documentation will live in the [unstable book] (or Cargo's [unstab [cargo-unstable]: https://doc.rust-lang.org/cargo/reference/unstable.html#allow-features [rustc book]: https://doc.rust-lang.org/nightly/rustc/ -The `--unstable-flags` option allows you to control precisely which unstable options are used by a given crate. +The `--unstable-flags` Rustc option allows you to control precisely which unstable options are used by a given crate. It's supported by rustc, and by all tools shipped with the official toolchain. - -Each flag takes one of the following strings as a value: -- An empty string, which indicates that no flags are allowed (default on stable) -- A comma-separated list of flag names. +For example, `rustdoc --unstable-flags=output-format --output-format=json` allows you to see Rustdoc's JSON output on stable. Flags are named after the CLI flag they enable. By implication, this means that CLI flags use dashes (`-`). +Flag values are not supported, only names. + +The `--unstable-flags` Cargo option is almost the same, but instructs Cargo which tools need to receive the option. +For example, `cargo doc --unstable-flags=rustdoc=output-format` will run `rustdoc --unstable-flags=output-format`. ## Stability policy @@ -131,19 +140,39 @@ but can help us find an alternative solution to your problem or otherwise improv # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -`unstable-flags` is a comma-separated list of CLI flag names. +`unstable-flags` is a comma-separated list of CLI flag names, with the leading `-Z` or `--` (if any) removed. Flag values are not supported, only names. +`unstable-flags` does not activate any flag on its own. +You still need to combine it with the `-Z` or unstable flag that you wish to enable. + +`unstable-flags` is accepted on all channels. +When it's not present, the default on stable/beta is to ban all unstable flags, +and the default on nightly is to allow all unstable flags. + +Stable/beta Rustfmt now errors instead of warning when an unstable option is set without also setting `--unstable-flags`. + `cargo --unstable-flags` applies to both Cargo and all tools it spawns. That is, it passes the flag to Rustc when Rustc is spawned. This applies to all packages, not just the current workspace. In practice, the only tool passing unstable flags to Cargo is `cargo-semver-checks`, which is building all dependencies in any case. +Build scripts cannot set these flags; `cargo::rustc-flags` continues to only accept `-l` and `-L` flags. + If `unstable-flags` is passed multiple times, the *intersection* of all values is used, not the union. This matters in cases where two parties don't trust each other, such as running `cargo build --unstable-flags=x` in a workspace with `build.rustflags=--unstable-flags=x,y`. -Unrecognized flag names in `unstable-flags` are ignored. -As a quality-of-implementation concern, the tool should warn if an unrecognized flag is passed. +Unrecognized flag names in `unstable-flags` are a hard error. + +Each non-Cargo flag takes one of the following strings as a value: +- An empty string, which indicates that no flags are allowed (default on stable) +- A comma-separated list of flag names. + +Each Cargo flag takes a value that starts with a tool name, then the string '=', then a valid value for a non-Cargo flag. +Tool names are the name of the exact binary that will be spawned: `rustdoc`, `clippy-driver`, etc. +If `RUSTC` or `RUSTDOC` is set, the tool name is still `rustc`/`rustdoc`, not the overridden value. +If `RUSTC_WRAPPER` or `RUSTC_WORKSPACE_WRAPPER` is set, the union of the flags for `rustc` and the wrapper are passed. + ## Implementation notes @@ -151,7 +180,7 @@ As a quality-of-implementation concern, the tool should warn if an unrecognized Currently, changing `RUSTC_BOOTSTRAP` does not invalidate Cargo's build cache by itself, but in practice can cause build scripts deep in the dependency tree to re-run. With `--unstable-flags`, we separate the mechanism for enabling flags from the mechanism for enabling lang features, -greatly decreasing how often build scripts run. +greatly decreasing how often build scripts that detect `RUSTC_BOOTSTRAP` rerun. We suggest, but do not require, that the flag is made part of the fingerprint tracking, not unit cache tracking, so that changing the enabled features overwrites the cache rather than adding to it. @@ -163,10 +192,6 @@ so that changing the enabled features overwrites the cache rather than adding to But people are doing that anyway, and keeping the status quo does not help us prevent them, while causing many other issues. - Note that while this could be seen as encouragement at the *policy* level, it's actually more restrictive at the *technical* level, since it requires people to make an exhaustive list of all features they're using. -- Rustfmt is often run automatically by editor plugins, not explicitly. - Additionally, right now rustfmt warns and continues when a feature gate is enabled on stable, which means the whole codebase gets reformatted. - We should make sure rustfmt is changed to instead give a hard error when the feature gate is disabled, which will avoid editors accidentally reformatting the whole codebase. - [The rustfmt team intends to fix this](https://github.com/rust-lang/rustfmt/issues/5022). - This may make it less likely that people help stabilize features. But stabilizing features is [very very hard](https://medium.com/@ElizAyer/organizational-boundary-problems-too-many-cooks-or-not-enough-kitchens-2ddedc6de26a), and in the meantime people have very little recourse when they need to use an unstable feature. @@ -222,7 +247,11 @@ This cannot be done in a library or macro. # Unresolved questions [unresolved-questions]: #unresolved-questions -Will this cause flags to be de-facto stable, even moreso than they are now? +- Will this cause flags to be de-facto stable, even moreso than they are now? +- This RFC frames `--unstable-flag` as an unstable feature. + Can we follow through on that in practice? + How badly will things break if we eventually remove it? +- Should we allow `name=value` filtering from the start, rather than deferring it to an extension? [response files]: https://doc.rust-lang.org/rustc/command-line-arguments.html#path-load-command-line-flags-from-a-path From 02aa99203309c9a4e5924ffabf8594ece204e4e3 Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 14:48:08 +0200 Subject: [PATCH 11/25] unstable-flags -> allow-flags --- text/0000-rustc-bootstrap.md | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index dd2ef198290..8c782cbe947 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -1,4 +1,4 @@ -- Feature Name: `unstable-flags` +- Feature Name: `allow-flags` - Start Date: 2025-05-18 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) @@ -11,8 +11,8 @@ still requiring feature gates, but allowing feature gates to be enabled on stabl It does so in two ways: 1. Extend `-Z unstable-options` to take a list of option names, rather than being a simple boolean. - Then, rename it to `--unstable-flags`. `unstable-flags` is always available, even on stable. -2. Add a new `--unstable-flags` flag to Cargo, which propagates it to all invoked commands with proper caching. + Then, rename it to `--allow-flags`. `allow-flags` is always available, even on stable. +2. Add a new `--allow-flags` flag to Cargo, which propagates it to all invoked commands with proper caching. 3. Change Rustfmt to give a hard error if a feature gate is disabled. This RFC is *not* intended as a general purpose mechanism for Rust developers to use nightly features on stable; @@ -108,16 +108,16 @@ The following documentation will live in the [unstable book] (or Cargo's [unstab [cargo-unstable]: https://doc.rust-lang.org/cargo/reference/unstable.html#allow-features [rustc book]: https://doc.rust-lang.org/nightly/rustc/ -The `--unstable-flags` Rustc option allows you to control precisely which unstable options are used by a given crate. +The `--allow-flags` Rustc option allows you to control precisely which unstable options are used by a given crate. It's supported by rustc, and by all tools shipped with the official toolchain. -For example, `rustdoc --unstable-flags=output-format --output-format=json` allows you to see Rustdoc's JSON output on stable. +For example, `rustdoc --allow-flags=output-format --output-format=json` allows you to see Rustdoc's JSON output on stable. Flags are named after the CLI flag they enable. By implication, this means that CLI flags use dashes (`-`). Flag values are not supported, only names. -The `--unstable-flags` Cargo option is almost the same, but instructs Cargo which tools need to receive the option. -For example, `cargo doc --unstable-flags=rustdoc=output-format` will run `rustdoc --unstable-flags=output-format`. +The `--allow-flags` Cargo option is almost the same, but instructs Cargo which tools need to receive the option. +For example, `cargo doc --allow-flags=rustdoc=output-format` will run `rustdoc --allow-flags=output-format`. ## Stability policy @@ -129,7 +129,7 @@ Although we do not take technical measures to prevent it from being used, we str If at all possible, please contribute to stabilizing the features you care about instead of bypassing the Rust project's stability policy. If you do use this to enable an unstable feature, please contact a member of the project who works on the feature in question, so that we know who is exposed to breakage. -For example, if you are using `rustdoc --unstable-flags=output-format`, reach out to [Alona Enraght-Moony][alona] (the maintainer of rustdoc-json). +For example, if you are using `rustdoc --allow-flags=output-format`, reach out to [Alona Enraght-Moony][alona] (the maintainer of rustdoc-json). If you do not know who to contact, ask on [Zulip]. Contacting a maintainer provides no stability guarantees and does not mean the maintainer will agree to work with you, but can help us find an alternative solution to your problem or otherwise improve the unstable feature you are using. @@ -140,29 +140,29 @@ but can help us find an alternative solution to your problem or otherwise improv # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -`unstable-flags` is a comma-separated list of CLI flag names, with the leading `-Z` or `--` (if any) removed. +`allow-flags` is a comma-separated list of CLI flag names, with the leading `-Z` or `--` (if any) removed. Flag values are not supported, only names. -`unstable-flags` does not activate any flag on its own. +`allow-flags` does not activate any flag on its own. You still need to combine it with the `-Z` or unstable flag that you wish to enable. -`unstable-flags` is accepted on all channels. +`allow-flags` is accepted on all channels. When it's not present, the default on stable/beta is to ban all unstable flags, and the default on nightly is to allow all unstable flags. -Stable/beta Rustfmt now errors instead of warning when an unstable option is set without also setting `--unstable-flags`. +Stable/beta Rustfmt now errors instead of warning when an unstable option is set without also setting `--allow-flags`. -`cargo --unstable-flags` applies to both Cargo and all tools it spawns. +`cargo --allow-flags` applies to both Cargo and all tools it spawns. That is, it passes the flag to Rustc when Rustc is spawned. This applies to all packages, not just the current workspace. In practice, the only tool passing unstable flags to Cargo is `cargo-semver-checks`, which is building all dependencies in any case. Build scripts cannot set these flags; `cargo::rustc-flags` continues to only accept `-l` and `-L` flags. -If `unstable-flags` is passed multiple times, the *intersection* of all values is used, not the union. -This matters in cases where two parties don't trust each other, such as running `cargo build --unstable-flags=x` in a workspace with `build.rustflags=--unstable-flags=x,y`. +If `allow-flags` is passed multiple times, the *intersection* of all values is used, not the union. +This matters in cases where two parties don't trust each other, such as running `cargo build --allow-flags=x` in a workspace with `build.rustflags=--allow-flags=x,y`. -Unrecognized flag names in `unstable-flags` are a hard error. +Unrecognized flag names in `allow-flags` are a hard error. Each non-Cargo flag takes one of the following strings as a value: - An empty string, which indicates that no flags are allowed (default on stable) @@ -179,7 +179,7 @@ If `RUSTC_WRAPPER` or `RUSTC_WORKSPACE_WRAPPER` is set, the union of the flags f ### Caching Currently, changing `RUSTC_BOOTSTRAP` does not invalidate Cargo's build cache by itself, but in practice can cause build scripts deep in the dependency tree to re-run. -With `--unstable-flags`, we separate the mechanism for enabling flags from the mechanism for enabling lang features, +With `--allow-flags`, we separate the mechanism for enabling flags from the mechanism for enabling lang features, greatly decreasing how often build scripts that detect `RUSTC_BOOTSTRAP` rerun. We suggest, but do not require, that the flag is made part of the fingerprint tracking, not unit cache tracking, @@ -259,9 +259,9 @@ This cannot be done in a library or macro. [future-possibilities]: #future-possibilities - Rename RUSTC_BOOTSTRAP to a name that makes more sense, such as `RUSTC_ALLOW_ALL_FEATURES`. -- We could split unstable flags into "alpha" and "beta", and only allow the latter to be enabled with `--unstable-flags`. +- We could split unstable flags into "alpha" and "beta", and only allow the latter to be enabled with `--allow-flags`. Additionally, we could enable beta flags by default on the beta channel. - We could add a version scheme to unstable flags, such that the opt-in has to specify exactly which version of the feature it expects (and gets a hard error if its expected version doesn't match the version implemented in the compiler). - The syntax for the opt-in would look like `--unstable-flags=output-format=2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. + The syntax for the opt-in would look like `--allow-flags=output-format=2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. To encourage project contributors to bump the version, we could remind them (e.g. in a Github comment when a PR is opened) whenever a test that uses the feature is modified. From 7e2b3ac3026edfdc04124e2652a6a2c60e9fb2c5 Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 14:49:26 +0200 Subject: [PATCH 12/25] fix syntax --- text/0000-rustc-bootstrap.md | 39 +++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 8c782cbe947..83591ca92f1 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -140,6 +140,8 @@ but can help us find an alternative solution to your problem or otherwise improv # Reference-level explanation [reference-level-explanation]: #reference-level-explanation +## Shared rules + `allow-flags` is a comma-separated list of CLI flag names, with the leading `-Z` or `--` (if any) removed. Flag values are not supported, only names. @@ -150,29 +152,37 @@ You still need to combine it with the `-Z` or unstable flag that you wish to ena When it's not present, the default on stable/beta is to ban all unstable flags, and the default on nightly is to allow all unstable flags. -Stable/beta Rustfmt now errors instead of warning when an unstable option is set without also setting `--allow-flags`. - -`cargo --allow-flags` applies to both Cargo and all tools it spawns. -That is, it passes the flag to Rustc when Rustc is spawned. -This applies to all packages, not just the current workspace. -In practice, the only tool passing unstable flags to Cargo is `cargo-semver-checks`, which is building all dependencies in any case. - -Build scripts cannot set these flags; `cargo::rustc-flags` continues to only accept `-l` and `-L` flags. +Unrecognized flag names in `allow-flags` are a hard error. -If `allow-flags` is passed multiple times, the *intersection* of all values is used, not the union. -This matters in cases where two parties don't trust each other, such as running `cargo build --allow-flags=x` in a workspace with `build.rustflags=--allow-flags=x,y`. +## Non-cargo rules -Unrecognized flag names in `allow-flags` are a hard error. +Stable/beta Rustfmt now errors instead of warning when an unstable option is set without also setting `--allow-flags`. Each non-Cargo flag takes one of the following strings as a value: - An empty string, which indicates that no flags are allowed (default on stable) - A comma-separated list of flag names. +If `allow-flags` is passed multiple times, the *intersection* of all values is used, not the union. +This matters in cases where two parties don't trust each other, such as running `cargo build --allow-flags=rustc=x` in a workspace with `build.rustflags=--allow-flags=x,y`: this should be equivalent to `rustc --allow-flags=x`. + +## Cargo rules + +Build scripts cannot set these flags; `cargo::rustc-flags` continues to only accept `-l` and `-L` flags. + Each Cargo flag takes a value that starts with a tool name, then the string '=', then a valid value for a non-Cargo flag. Tool names are the name of the exact binary that will be spawned: `rustdoc`, `clippy-driver`, etc. If `RUSTC` or `RUSTDOC` is set, the tool name is still `rustc`/`rustdoc`, not the overridden value. -If `RUSTC_WRAPPER` or `RUSTC_WORKSPACE_WRAPPER` is set, the union of the flags for `rustc` and the wrapper are passed. +If `RUSTC_WRAPPER` or `RUSTC_WORKSPACE_WRAPPER` is set, the intersection of the flags for `rustc` and the wrapper are passed; this requires additional work from the user but avoids silently passing unstable flags to more tools than intended. + +If `allow-flags` is passed multiple times, tools are unioned, but values are intersected. +In other words, `cargo doc --allow-flags=rustc=x --allow-flags=rustdoc=y` will pass `--allow-flags=x` to Rustc and `--allow-flags=y` to Rustdoc. + +`cargo --allow-flags` applies to both Cargo and all tools it spawns. +That is, it passes the flag to Rustc when Rustc is spawned. +This applies to all packages, not just the current workspace. +In practice, the only tool passing unstable flags to Cargo is `cargo-semver-checks`, which is building all dependencies in any case. +Unrecognized tool names are an error. ## Implementation notes @@ -248,7 +258,8 @@ This cannot be done in a library or macro. [unresolved-questions]: #unresolved-questions - Will this cause flags to be de-facto stable, even moreso than they are now? -- This RFC frames `--unstable-flag` as an unstable feature. + We could emit a future-compat warning whenever this flag is used; is that sufficient? +- This RFC frames `--allow-flags` as an unstable feature. Can we follow through on that in practice? How badly will things break if we eventually remove it? - Should we allow `name=value` filtering from the start, rather than deferring it to an extension? @@ -263,5 +274,5 @@ This cannot be done in a library or macro. Additionally, we could enable beta flags by default on the beta channel. - We could add a version scheme to unstable flags, such that the opt-in has to specify exactly which version of the feature it expects (and gets a hard error if its expected version doesn't match the version implemented in the compiler). - The syntax for the opt-in would look like `--allow-flags=output-format=2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. + The syntax for the opt-in would look like `--allow-flags=output-format@2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. To encourage project contributors to bump the version, we could remind them (e.g. in a Github comment when a PR is opened) whenever a test that uses the feature is modified. From 1b58ac182264a08dbac89e4bee31ae975bbbe743 Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 15:08:13 +0200 Subject: [PATCH 13/25] mention that cargo is a tool --- text/0000-rustc-bootstrap.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 83591ca92f1..cb9998a634f 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -118,6 +118,7 @@ Flag values are not supported, only names. The `--allow-flags` Cargo option is almost the same, but instructs Cargo which tools need to receive the option. For example, `cargo doc --allow-flags=rustdoc=output-format` will run `rustdoc --allow-flags=output-format`. +You can use `cargo build --allow-flags=cargo=profile-hint-mostly-unused` to allow a flag in Cargo itself. ## Stability policy @@ -170,10 +171,12 @@ This matters in cases where two parties don't trust each other, such as running Build scripts cannot set these flags; `cargo::rustc-flags` continues to only accept `-l` and `-L` flags. Each Cargo flag takes a value that starts with a tool name, then the string '=', then a valid value for a non-Cargo flag. +The tool can be `cargo`, in which case the flag applies to the unstable flags of Cargo itself. Tool names are the name of the exact binary that will be spawned: `rustdoc`, `clippy-driver`, etc. If `RUSTC` or `RUSTDOC` is set, the tool name is still `rustc`/`rustdoc`, not the overridden value. If `RUSTC_WRAPPER` or `RUSTC_WORKSPACE_WRAPPER` is set, the intersection of the flags for `rustc` and the wrapper are passed; this requires additional work from the user but avoids silently passing unstable flags to more tools than intended. + If `allow-flags` is passed multiple times, tools are unioned, but values are intersected. In other words, `cargo doc --allow-flags=rustc=x --allow-flags=rustdoc=y` will pass `--allow-flags=x` to Rustc and `--allow-flags=y` to Rustdoc. From 73a032738d07aa3de374fd2bff19c952c4aaada8 Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 15:13:15 +0200 Subject: [PATCH 14/25] mention libtest --- text/0000-rustc-bootstrap.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index cb9998a634f..1016774d27b 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -159,6 +159,8 @@ Unrecognized flag names in `allow-flags` are a hard error. Stable/beta Rustfmt now errors instead of warning when an unstable option is set without also setting `--allow-flags`. +libtest runners accept `--allow-flags`. + Each non-Cargo flag takes one of the following strings as a value: - An empty string, which indicates that no flags are allowed (default on stable) - A comma-separated list of flag names. From 6353abf11da599225ee3775a7eda3adc44214d5b Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 16:02:12 +0200 Subject: [PATCH 15/25] mention nix as prior art --- text/0000-rustc-bootstrap.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 1016774d27b..73cddc8679e 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -249,6 +249,10 @@ This cannot be done in a library or macro. - Python distributes separate binaries that [disable the GIL by default][free-threaded python]. Python also has [`-X` flags][python-x], which do not have feature gates. - Scala allows marking library APIs as [experimental]. Experimental APIs are "infectious" - any code using an experimental API must also be marked as experimental. Additionally, experimental APIs can be upgraded to [preview], meaning that they are guaranteed to exist in the future but might change their exact details. Unlike experimental APIs, preview APIs are not infectious. To enable experimental/preview features for all functions in a module at once, the compiler takes `-experimental`/`-preview` flags. - Kubernetes allows [enabling features][kubernetes-features] with `--feature-gates=Feature1=true,Feature2=true`. Additionally, it splits features into "Alpha" (experimental, can be removed altogether) and "Beta" (enabled by default, tested, can be changed but not removed). +- Nix allows enabling features [in `nix.conf`](https://nix.dev/manual/nix/2.34/command-ref/conf-file.html#conf-accept-flake-config). + In practice, this results in people widely using features throughout the ecosystem. + We take this as a lesson telling us that an opt-in in `[workspace.unstable]` is not sufficient, + that there needs to be a reminder on each command that unstable features are active. [`goexperiment` module]: https://pkg.go.dev/internal/goexperiment [java-x]: https://docs.oracle.com/cd/E13150_01/jrockit_jvm/jrockit/jrdocs/refman/optionX.html From 335061290717084a5d63acefb3ffa9e02934d8de Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 18:35:24 +0200 Subject: [PATCH 16/25] wording and typo fixes Co-authored-by: Alice Cecile Co-authored-by: Miguel Ojeda Co-authored-by: DAA <42379074+DaAlbrecht@users.noreply.github.com> --- text/0000-rustc-bootstrap.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 73cddc8679e..586cd7e2a5b 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -17,7 +17,7 @@ It does so in two ways: This RFC is *not* intended as a general purpose mechanism for Rust developers to use nightly features on stable; it's specifically targeted at build systems wrapping cargo, such as distro packagers, external tools shipped with the toolchain, and large projects that build a custom Rust toolchain from source. -As such, it does not attempt to address unstable lang features, which we consider adequately addressed by `RUSTC_BOOTSTRAP=crate_name`. +As such, it does not attempt to address the use of unstable lang features with a stable Rust compiler version, which we consider adequately addressed by `RUSTC_BOOTSTRAP=crate_name`. # Motivation [motivation]: #motivation @@ -25,7 +25,7 @@ As such, it does not attempt to address unstable lang features, which we conside ## Why allow using unstable features on stable? Rust's stability policy has two components: -1. To the extent possible, each unstable feature comes with a feature gate, and is disabled when that feature gate is inactive. [^1] +1. To the extent possible, each unstable feature comes with its own feature gate, and is disabled when that feature gate is inactive. [^1] 2. Enabling feature gates is only allowed on the nightly toolchain. [^1]: There are some exceptions to this, such as https://github.com/rust-lang/rust/issues/139892#issuecomment-2808505610. @@ -56,13 +56,13 @@ Some examples: Why are these uses ok? Three reasons: - Each of these, except for rustc_public, is an external tool, not a library. - They do not need unstable language features, only unstable tool output. + They do not need unstable language features, only unstable tool features. - Each of these tools accept responsibility for breakage. `semver-checks` and RfL both explicitly adapt to each new release of rustc, and their feedback on breakage is very useful for improving the features they use. rust-analyzer and RustRover don't break at all for `--print=cfg`—they're not using it in code, only in the CLI—and adapt to any changes in libtest json format. - These tools act as a "buffer" between other projects and breakage. - For example, semver-checks hides the breaking changes behind its own interface such that downstream projects are not affected. - Similar, RfL backports breakage fixes to stable branches such that old versions of the kernel keep building with new rust toolchains. + For example, `semver-checks` hides the breaking changes behind its own interface such that downstream projects are not affected. + Similarly, RfL backports breakage fixes to stable branches such that old versions of the kernel keep building with new rust toolchains. One might ask, well, maybe we are being too eager to gate things, but can't people just use nightly? There are some cases where switching to nightly is not realistic. From cfe31bc6da9896b30eeebc4faa5d3741c4c82e48 Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 16:20:28 +0200 Subject: [PATCH 17/25] clarity and wording --- text/0000-rustc-bootstrap.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 586cd7e2a5b..d20ec6783ef 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -13,7 +13,6 @@ It does so in two ways: 1. Extend `-Z unstable-options` to take a list of option names, rather than being a simple boolean. Then, rename it to `--allow-flags`. `allow-flags` is always available, even on stable. 2. Add a new `--allow-flags` flag to Cargo, which propagates it to all invoked commands with proper caching. -3. Change Rustfmt to give a hard error if a feature gate is disabled. This RFC is *not* intended as a general purpose mechanism for Rust developers to use nightly features on stable; it's specifically targeted at build systems wrapping cargo, such as distro packagers, external tools shipped with the toolchain, and large projects that build a custom Rust toolchain from source. @@ -73,15 +72,14 @@ There are some cases where switching to nightly is not realistic. ## Why this exact mechanism? -Currently, these tools use [`RUSTC_BOOTSTRAP=1`][rustc-bootstrap] as a workaround. But this workaround has many downsides: - -[rustc-bootstrap]: https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/rustc-bootstrap.html - -Enabling RUSTC_BOOTSTRAP for one part of the toolchain enables it for *all* parts of the toolchain; in particular: +Currently, these tools use [`RUSTC_BOOTSTRAP=1`][rustc-bootstrap] as a workaround. +But enabling RUSTC_BOOTSTRAP for one part of the toolchain enables it for *all* parts of the toolchain; in particular: - `proc-macro2` uses `cargo:rerun-on-env-changed=RUSTC_BOOTSTRAP`, causing cache thrashing whenever this env var changes. -- rust-analyzer wants to enable RUSTC_BOOTSTRAP only for cargo and libtest, but the variable enables features for rustc as well. +- rust-analyzer wants to enable `RUSTC_BOOTSTRAP` only for cargo and libtest, but the variable enables features for rustc as well. `RUSTFLAGS="-Z allow-features="` fixes this for lang features, but at the price of thrashing the cache; and there is no equivalent way to disable unstable CLI features. +[rustc-bootstrap]: https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/rustc-bootstrap.html + An important design constraint here is that the "end-user" (whoever is running the build) should always have control over which features are enabled. To the extent that tools act as a "buffer" between feature breakage and the end-user, they should only take responsibility for exactly the features whose breakage they know how to handle. From 22925678dafdbcc0d433ac2647383372196c6a1a Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 18:18:07 +0200 Subject: [PATCH 18/25] s/allow-flags/allow-unstable-flags/g --- text/0000-rustc-bootstrap.md | 48 ++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index d20ec6783ef..2f80a15d949 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -1,4 +1,4 @@ -- Feature Name: `allow-flags` +- Feature Name: `allow-unstable-flags` - Start Date: 2025-05-18 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) @@ -11,8 +11,8 @@ still requiring feature gates, but allowing feature gates to be enabled on stabl It does so in two ways: 1. Extend `-Z unstable-options` to take a list of option names, rather than being a simple boolean. - Then, rename it to `--allow-flags`. `allow-flags` is always available, even on stable. -2. Add a new `--allow-flags` flag to Cargo, which propagates it to all invoked commands with proper caching. + Then, rename it to `--allow-unstable-flags`. `allow-unstable-flags` is always available, even on stable. +2. Add a new `--allow-unstable-flags` flag to Cargo, which propagates it to all invoked commands with proper caching. This RFC is *not* intended as a general purpose mechanism for Rust developers to use nightly features on stable; it's specifically targeted at build systems wrapping cargo, such as distro packagers, external tools shipped with the toolchain, and large projects that build a custom Rust toolchain from source. @@ -106,17 +106,17 @@ The following documentation will live in the [unstable book] (or Cargo's [unstab [cargo-unstable]: https://doc.rust-lang.org/cargo/reference/unstable.html#allow-features [rustc book]: https://doc.rust-lang.org/nightly/rustc/ -The `--allow-flags` Rustc option allows you to control precisely which unstable options are used by a given crate. +The `--allow-unstable-flags` Rustc option allows you to control precisely which unstable options are used by a given crate. It's supported by rustc, and by all tools shipped with the official toolchain. -For example, `rustdoc --allow-flags=output-format --output-format=json` allows you to see Rustdoc's JSON output on stable. +For example, `rustdoc --allow-unstable-flags=output-format --output-format=json` allows you to see Rustdoc's JSON output on stable. Flags are named after the CLI flag they enable. By implication, this means that CLI flags use dashes (`-`). Flag values are not supported, only names. -The `--allow-flags` Cargo option is almost the same, but instructs Cargo which tools need to receive the option. -For example, `cargo doc --allow-flags=rustdoc=output-format` will run `rustdoc --allow-flags=output-format`. -You can use `cargo build --allow-flags=cargo=profile-hint-mostly-unused` to allow a flag in Cargo itself. +The `--allow-unstable-flags` Cargo option is almost the same, but instructs Cargo which tools need to receive the option. +For example, `cargo doc --allow-unstable-flags=rustdoc=output-format` will run `rustdoc --allow-unstable-flags=output-format`. +You can use `cargo build --allow-unstable-flags=cargo=profile-hint-mostly-unused` to allow a flag in Cargo itself. ## Stability policy @@ -128,7 +128,7 @@ Although we do not take technical measures to prevent it from being used, we str If at all possible, please contribute to stabilizing the features you care about instead of bypassing the Rust project's stability policy. If you do use this to enable an unstable feature, please contact a member of the project who works on the feature in question, so that we know who is exposed to breakage. -For example, if you are using `rustdoc --allow-flags=output-format`, reach out to [Alona Enraght-Moony][alona] (the maintainer of rustdoc-json). +For example, if you are using `rustdoc --allow-unstable-flags=output-format`, reach out to [Alona Enraght-Moony][alona] (the maintainer of rustdoc-json). If you do not know who to contact, ask on [Zulip]. Contacting a maintainer provides no stability guarantees and does not mean the maintainer will agree to work with you, but can help us find an alternative solution to your problem or otherwise improve the unstable feature you are using. @@ -141,30 +141,30 @@ but can help us find an alternative solution to your problem or otherwise improv ## Shared rules -`allow-flags` is a comma-separated list of CLI flag names, with the leading `-Z` or `--` (if any) removed. +`allow-unstable-flags` is a comma-separated list of CLI flag names, with the leading `-Z` or `--` (if any) removed. Flag values are not supported, only names. -`allow-flags` does not activate any flag on its own. +`allow-unstable-flags` does not activate any flag on its own. You still need to combine it with the `-Z` or unstable flag that you wish to enable. -`allow-flags` is accepted on all channels. +`allow-unstable-flags` is accepted on all channels. When it's not present, the default on stable/beta is to ban all unstable flags, and the default on nightly is to allow all unstable flags. -Unrecognized flag names in `allow-flags` are a hard error. +Unrecognized flag names in `allow-unstable-flags` are a hard error. ## Non-cargo rules -Stable/beta Rustfmt now errors instead of warning when an unstable option is set without also setting `--allow-flags`. +Stable/beta Rustfmt now errors instead of warning when an unstable option is set without also setting `--allow-unstable-flags`. -libtest runners accept `--allow-flags`. +libtest runners accept `--allow-unstable-flags`. Each non-Cargo flag takes one of the following strings as a value: - An empty string, which indicates that no flags are allowed (default on stable) - A comma-separated list of flag names. -If `allow-flags` is passed multiple times, the *intersection* of all values is used, not the union. -This matters in cases where two parties don't trust each other, such as running `cargo build --allow-flags=rustc=x` in a workspace with `build.rustflags=--allow-flags=x,y`: this should be equivalent to `rustc --allow-flags=x`. +If `allow-unstable-flags` is passed multiple times, the *intersection* of all values is used, not the union. +This matters in cases where two parties don't trust each other, such as running `cargo build --allow-unstable-flags=rustc=x` in a workspace with `build.rustflags=--allow-unstable-flags=x,y`: this should be equivalent to `rustc --allow-unstable-flags=x`. ## Cargo rules @@ -177,10 +177,10 @@ If `RUSTC` or `RUSTDOC` is set, the tool name is still `rustc`/`rustdoc`, not th If `RUSTC_WRAPPER` or `RUSTC_WORKSPACE_WRAPPER` is set, the intersection of the flags for `rustc` and the wrapper are passed; this requires additional work from the user but avoids silently passing unstable flags to more tools than intended. -If `allow-flags` is passed multiple times, tools are unioned, but values are intersected. -In other words, `cargo doc --allow-flags=rustc=x --allow-flags=rustdoc=y` will pass `--allow-flags=x` to Rustc and `--allow-flags=y` to Rustdoc. +If `allow-unstable-flags` is passed multiple times, tools are unioned, but values are intersected. +In other words, `cargo doc --allow-unstable-flags=rustc=x --allow-unstable-flags=rustdoc=y` will pass `--allow-unstable-flags=x` to Rustc and `--allow-unstable-flags=y` to Rustdoc. -`cargo --allow-flags` applies to both Cargo and all tools it spawns. +`cargo --allow-unstable-flags` applies to both Cargo and all tools it spawns. That is, it passes the flag to Rustc when Rustc is spawned. This applies to all packages, not just the current workspace. In practice, the only tool passing unstable flags to Cargo is `cargo-semver-checks`, which is building all dependencies in any case. @@ -192,7 +192,7 @@ Unrecognized tool names are an error. ### Caching Currently, changing `RUSTC_BOOTSTRAP` does not invalidate Cargo's build cache by itself, but in practice can cause build scripts deep in the dependency tree to re-run. -With `--allow-flags`, we separate the mechanism for enabling flags from the mechanism for enabling lang features, +With `--allow-unstable-flags`, we separate the mechanism for enabling flags from the mechanism for enabling lang features, greatly decreasing how often build scripts that detect `RUSTC_BOOTSTRAP` rerun. We suggest, but do not require, that the flag is made part of the fingerprint tracking, not unit cache tracking, @@ -266,7 +266,7 @@ This cannot be done in a library or macro. - Will this cause flags to be de-facto stable, even moreso than they are now? We could emit a future-compat warning whenever this flag is used; is that sufficient? -- This RFC frames `--allow-flags` as an unstable feature. +- This RFC frames `--allow-unstable-flags` as an unstable feature. Can we follow through on that in practice? How badly will things break if we eventually remove it? - Should we allow `name=value` filtering from the start, rather than deferring it to an extension? @@ -277,9 +277,9 @@ This cannot be done in a library or macro. [future-possibilities]: #future-possibilities - Rename RUSTC_BOOTSTRAP to a name that makes more sense, such as `RUSTC_ALLOW_ALL_FEATURES`. -- We could split unstable flags into "alpha" and "beta", and only allow the latter to be enabled with `--allow-flags`. +- We could split unstable flags into "alpha" and "beta", and only allow the latter to be enabled with `--allow-unstable-flags`. Additionally, we could enable beta flags by default on the beta channel. - We could add a version scheme to unstable flags, such that the opt-in has to specify exactly which version of the feature it expects (and gets a hard error if its expected version doesn't match the version implemented in the compiler). - The syntax for the opt-in would look like `--allow-flags=output-format@2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. + The syntax for the opt-in would look like `--allow-unstable-flags=output-format@2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. To encourage project contributors to bump the version, we could remind them (e.g. in a Github comment when a PR is opened) whenever a test that uses the feature is modified. From b1ecb20b3e47256695743ddf260189045638ae6f Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 18:18:40 +0200 Subject: [PATCH 19/25] address a couple of Ojeda's concerns --- text/0000-rustc-bootstrap.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 2f80a15d949..a31216e246a 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -12,10 +12,12 @@ still requiring feature gates, but allowing feature gates to be enabled on stabl It does so in two ways: 1. Extend `-Z unstable-options` to take a list of option names, rather than being a simple boolean. Then, rename it to `--allow-unstable-flags`. `allow-unstable-flags` is always available, even on stable. + For example: `rustc --allow-unstable-flags=annotate-moves,binary-dep-depinfo` 2. Add a new `--allow-unstable-flags` flag to Cargo, which propagates it to all invoked commands with proper caching. + For example: `cargo build --allow-unstable-flags=rustc=annotate-moves --allow-unstable-flags=cargo=build-dir-new-layout`. This RFC is *not* intended as a general purpose mechanism for Rust developers to use nightly features on stable; -it's specifically targeted at build systems wrapping cargo, such as distro packagers, external tools shipped with the toolchain, and large projects that build a custom Rust toolchain from source. +it's specifically targeted at build systems wrapping Rustc or Cargo, such as distro packagers, external tools shipped with the toolchain, and large projects that build a custom Rust toolchain from source. As such, it does not attempt to address the use of unstable lang features with a stable Rust compiler version, which we consider adequately addressed by `RUSTC_BOOTSTRAP=crate_name`. # Motivation @@ -51,8 +53,11 @@ Some examples: - `cargo semver-checks` needs `rustdoc --output-format=json` in order to work at all. - Rust for Linux (RfL) needs a way to build a custom version of core. In particular, they mentioned they need to disable float support, because using float registers can cause unsoundness. + They also have a [much larger list][rfl-wishlist] of all unstable features used; they won't get away from unstable any time soon. - `rustc_public`'s entire mission is to wrap unstable APIs with stable ones and therefore needs access to all `rustc_private` features. +[rfl-wishlist]: https://github.com/Rust-for-Linux/linux/issues/2 + Why are these uses ok? Three reasons: - Each of these, except for rustc_public, is an external tool, not a library. They do not need unstable language features, only unstable tool features. From 39968d695a3c2f5c1d03796105d017e72f36fb90 Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 18:41:49 +0200 Subject: [PATCH 20/25] extend motivation section --- text/0000-rustc-bootstrap.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index a31216e246a..8d6edbb7478 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -83,6 +83,8 @@ But enabling RUSTC_BOOTSTRAP for one part of the toolchain enables it for *all* - rust-analyzer wants to enable `RUSTC_BOOTSTRAP` only for cargo and libtest, but the variable enables features for rustc as well. `RUSTFLAGS="-Z allow-features="` fixes this for lang features, but at the price of thrashing the cache; and there is no equivalent way to disable unstable CLI features. +`--allow-unstable-features` extends this to CLI features, allowing projects to opt-in to only the unstability they choose to. + [rustc-bootstrap]: https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/rustc-bootstrap.html An important design constraint here is that the "end-user" (whoever is running the build) should always have control over which features are enabled. From 4c5aefe89c58ceba7e67ef32f62ef278155778f1 Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 18:41:49 +0200 Subject: [PATCH 21/25] add `clang -fexperimental-*` as prior art --- text/0000-rustc-bootstrap.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 8d6edbb7478..03b5da33431 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -249,6 +249,10 @@ This cannot be done in a library or macro. # Prior art [prior-art]: #prior-art +- Clang has `-fexperimental-*` flags. These have no feature gate. + I was not able to find information about their policy for breakage, nor whether they've removed these flags in the past. + - Clang also has `-fexperimental-library`, which is similar to Rust's library feature gates. That's documented as follows: + > Control whether unstable and experimental library features are enabled. This option enables various library features that are either experimental (also known as TSes), or have been but are not stable yet in the selected Standard Library implementation. It is not recommended to use this option in production code, since neither ABI nor API stability are guaranteed. This is intended to provide a preview of features that will ship in the future for experimentation purposes - Go has the [`goexperiment` module]. This is enabled at compile time with an environment variable that takes a list of features to enable. - Java has implementation-specific [`-X` flags][java-x] (which are roughly equivalent to `-Z` flags in Rust). They do not have feature gates. Java also has [preview features], which are guaranteed to exist in all implementations, but require opting in with `--enable-preview` *both* at compile time (with `javac`) and at runtime (with the `java` binary). - Python distributes separate binaries that [disable the GIL by default][free-threaded python]. Python also has [`-X` flags][python-x], which do not have feature gates. From e8709a2b1a2adb5701d785216d19749630b9088b Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 18:55:38 +0200 Subject: [PATCH 22/25] extend motivation and put it directly in the summary --- text/0000-rustc-bootstrap.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 03b5da33431..746fed45831 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -16,6 +16,11 @@ It does so in two ways: 2. Add a new `--allow-unstable-flags` flag to Cargo, which propagates it to all invoked commands with proper caching. For example: `cargo build --allow-unstable-flags=rustc=annotate-moves --allow-unstable-flags=cargo=build-dir-new-layout`. +The goal of this flag is two-fold: +1. *Harm reduction*: There are already tools bypassing our stability policy, in a way that's painful for both them and the people using them. + We aim to reduce the amount of breakage and unnecessary rebuilds that causes. +1. *Feedback on unstable features*: By encouraging experimentation on stable, we hope to get bug reports from a wider variety of environments than "developer machines running nightly with rustup". + This RFC is *not* intended as a general purpose mechanism for Rust developers to use nightly features on stable; it's specifically targeted at build systems wrapping Rustc or Cargo, such as distro packagers, external tools shipped with the toolchain, and large projects that build a custom Rust toolchain from source. As such, it does not attempt to address the use of unstable lang features with a stable Rust compiler version, which we consider adequately addressed by `RUSTC_BOOTSTRAP=crate_name`. @@ -82,6 +87,7 @@ But enabling RUSTC_BOOTSTRAP for one part of the toolchain enables it for *all* - `proc-macro2` uses `cargo:rerun-on-env-changed=RUSTC_BOOTSTRAP`, causing cache thrashing whenever this env var changes. - rust-analyzer wants to enable `RUSTC_BOOTSTRAP` only for cargo and libtest, but the variable enables features for rustc as well. `RUSTFLAGS="-Z allow-features="` fixes this for lang features, but at the price of thrashing the cache; and there is no equivalent way to disable unstable CLI features. + It's also common for crates to have broken build scripts which do version detection instead of feature detection; in that case, enabling `RUSTC_BOOTSTRAP` with `allow-features` will break the build. `--allow-unstable-features` extends this to CLI features, allowing projects to opt-in to only the unstability they choose to. From 09393053115db64a33cb6aed65b71b6aeb0d976e Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 18:55:38 +0200 Subject: [PATCH 23/25] change "end-user" to "self-contained build" --- text/0000-rustc-bootstrap.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 746fed45831..36b43890b60 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -93,7 +93,7 @@ But enabling RUSTC_BOOTSTRAP for one part of the toolchain enables it for *all* [rustc-bootstrap]: https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/rustc-bootstrap.html -An important design constraint here is that the "end-user" (whoever is running the build) should always have control over which features are enabled. +An important design constraint here is that each "self-contained build"[^3] should always have control over which features are enabled. To the extent that tools act as a "buffer" between feature breakage and the end-user, they should only take responsibility for exactly the features whose breakage they know how to handle. @@ -300,3 +300,7 @@ This cannot be done in a library or macro. (and gets a hard error if its expected version doesn't match the version implemented in the compiler). The syntax for the opt-in would look like `--allow-unstable-flags=output-format@2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. To encourage project contributors to bump the version, we could remind them (e.g. in a Github comment when a PR is opened) whenever a test that uses the feature is modified. + +[^3]: "Self-contained" here means that the project is running in a known environment with control over its own invocations; + this is true for whoever runs `cargo build` on a binary, and for Rust for Linux, but not for `cargo build` on a library. + From 765363591972d1a57b49ddf56908a6325c41bb2e Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 19:30:58 +0200 Subject: [PATCH 24/25] fix some typos --- text/0000-rustc-bootstrap.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index 36b43890b60..f9b4b7b53c1 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -89,7 +89,7 @@ But enabling RUSTC_BOOTSTRAP for one part of the toolchain enables it for *all* `RUSTFLAGS="-Z allow-features="` fixes this for lang features, but at the price of thrashing the cache; and there is no equivalent way to disable unstable CLI features. It's also common for crates to have broken build scripts which do version detection instead of feature detection; in that case, enabling `RUSTC_BOOTSTRAP` with `allow-features` will break the build. -`--allow-unstable-features` extends this to CLI features, allowing projects to opt-in to only the unstability they choose to. +`--allow-unstable-features` extends this to CLI features, allowing projects to opt-in to only the instability they choose to. [rustc-bootstrap]: https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/rustc-bootstrap.html @@ -106,9 +106,9 @@ because there's no possibility there of the library acting as a buffer between o ## Why change Rustfmt? Rustfmt is often run automatically by editor plugins, not explicitly. -Additionally, right now rustfmt warns and continues when a feature gate is enabled on stable, which means the whole codebase gets reformatted. +Additionally, right now Rustfmt warns and continues when a feature gate is enabled on stable, which means the whole codebase gets reformatted. Changing Rustfmt to instead give a hard error when the feature gate is disabled avoids editors accidentally reformatting the whole codebase. -[The rustfmt team already intends to fix this](https://github.com/rust-lang/rustfmt/issues/5022). +[The Rustfmt team already intends to fix this](https://github.com/rust-lang/rustfmt/issues/5022). # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -299,7 +299,7 @@ This cannot be done in a library or macro. - We could add a version scheme to unstable flags, such that the opt-in has to specify exactly which version of the feature it expects (and gets a hard error if its expected version doesn't match the version implemented in the compiler). The syntax for the opt-in would look like `--allow-unstable-flags=output-format@2` (3, 4, ...), which is backwards-compatible with the current RFC proposal. - To encourage project contributors to bump the version, we could remind them (e.g. in a Github comment when a PR is opened) whenever a test that uses the feature is modified. + To encourage project contributors to bump the version, we could remind them (e.g. in a GitHub comment when a PR is opened) whenever a test that uses the feature is modified. [^3]: "Self-contained" here means that the project is running in a known environment with control over its own invocations; this is true for whoever runs `cargo build` on a binary, and for Rust for Linux, but not for `cargo build` on a library. From d6779ea48b0a166496688e1b15df900be7ef3f26 Mon Sep 17 00:00:00 2001 From: jyn Date: Wed, 27 May 2026 23:01:09 +0200 Subject: [PATCH 25/25] remove future-incompat force-warn --- text/0000-rustc-bootstrap.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/text/0000-rustc-bootstrap.md b/text/0000-rustc-bootstrap.md index f9b4b7b53c1..54826322908 100644 --- a/text/0000-rustc-bootstrap.md +++ b/text/0000-rustc-bootstrap.md @@ -249,6 +249,11 @@ so that changing the enabled features overwrites the cache rather than adding to - We can separate the toolchain into "stable" and "unstable" channels, and tell distros to package both. This is a big ask from distros, and does not actually help with many of the problems (for example cargo semver-checks cannot rely on it being installed, and rust-analyzer will still have caching issues). +- We could emit a future-compat hard warning whenever this flag is used. + This will cause problems for toolchains like Rust-for-Linux where it's non-trivial to parse and post-process JSON output. + It also doesn't seem necessary: the thing we want to avoid is people using nightly features without *knowing* it, + and preventing them from being automatically set for a library crate is sufficient for that. + (`.cargo/config.toml` can set RUSTFLAGS automatically, but it can't do so outside of the current workspace.) This cannot be done in a library or macro. @@ -282,7 +287,6 @@ This cannot be done in a library or macro. [unresolved-questions]: #unresolved-questions - Will this cause flags to be de-facto stable, even moreso than they are now? - We could emit a future-compat warning whenever this flag is used; is that sufficient? - This RFC frames `--allow-unstable-flags` as an unstable feature. Can we follow through on that in practice? How badly will things break if we eventually remove it?