PSA: Important info about rustc's new feature staging

Rust feature staging and you

Recently I've been overhauling the mechanisms for introducing unstable
features to both the Rust language and libraries. Part of that work
landed for the 1.0.0-alpha release when we started producing warnings
about using unstable library APIs, which I'm sure you noticed. Now the
transition to the system described in RFC 507 is nearing
completion, so it's time to remind everybody what's happening and how
to cope with it.

We want to evolve the Rust process such that new features progress
into the language in a controlled and predictable way, so that there
are yet fewer suprizes about what is or isn't ready for use, and risk
of new features becoming de-facto stable through early adoption is
minimized. From now on every feature going into either the Rust
language or the standard library is identified by a feature name,
extending the feature-gating mechanism used by the language to the
standard library.

For the remainder of the current development cycle, the nightly
compiler will print a warning for all code that uses unstable features:

/home/brian/test.rs:1:22: 1:44 warning: use of unstable library feature 'core'
/home/brian/test.rs:1 fn main() { unsafe { std::intrinsics::abort() } }
                                           ^~~~~~~~~~~~~~~~~~~~~~
/home/brian/test.rs:1:22: 1:44 help: add #![feature(core)] to the crate attributes to silence this warning
/home/brian/test.rs:1 fn main() { unsafe { std::intrinsics::abort() } }
                                           ^~~~~~~~~~~~~~~~~~~~~~

During the first 1.0 beta cycle these will be converted to errors on
the nightly channel, and authors will be required to explicitly opt-in
to use of unstable libraries to keep their code compiling. On the beta
and stable channels it will not be possible to use unstable
features. We hope that between upstream stabilization and feedback
from downstream authors we will converge upon a clearly-defined subset
of the Rust libraries that are stable and that can support a
substantial collection of the crates in the Cargo registry.

Feature staging for crate authors

Taking Rust from its historical posture in which unstable features are
always available to one in which only stable features are available in
production releases is a big and painful change to work through. As a
crate author here's what you need to know:

To use unstable features you must always declare so by putting
#![feature(foo)] at the top of your crate. While this has been true
for language features for a long time, it is now true for the standard
library as well. You are going to be doing a lot more of this in the
short term until std stability for 1.0 has completely shaken out -
hang in there!

Once the betas start and using unstable features without opting in
becomes an error (wheras now it's a warning), it will be trivial to
determine whether your code is using the stable - and backwards
compatible - subset of the language: any Rust crate that contains no
#![feature] attributes is written in a stable dialect of Rust.

As introduced, the current unstable library features are
rather-coarse-grained. Some of those likely to impact existing code
include:

  • io - All of std::io
  • os - All of std::os
  • path - All of std::path
  • hash - All of std::hash
  • collections - The collections crate and collections reexported
    from std
  • rand - The rand crate and its componentsn reexported from std
  • core - Everything else that's unstable in core
  • std_misc - Everything else that's unstable in std
  • test - The test crate
  • rustc_private - In-tree components that only the compiler should
    be using

During their unstable development feature names may change
incompatibly, and in particular be divided into smaller subsets with
different unique feature names. You are likely to see more features
appear in the coming weeks as the remaining unstable APIs are
scrutinized.

In this initial revision the core and std_misc features cover a
wide range of functionality - they are 'catch-all' features for those
APIs that haven't been properly categorized. Being forced to enable
one of these features may indicate a 'gap' in the 1.0 stability story.
As a crate author if you see a logical subdivision of functionality
that is currently assigned the core or std_misc feature please
suggest the feature be created, either here or on the issue tracker.
Having more named features will help us better track library
stabilization.

If this seems painful try to be reassured that we dearly want your
crates to work on the stable release channel and will do everything we
can to make that happen as fast as reasonably possible. In the coming
weeks there will be a concerted effort to lift as much of the cargo
ecosystem onto the stable subset of the language as possible.

Deprecation continues to work the same as it always has, via the
'deprecated' lint, so deprecated APIs can be opted into with
#[allow(deprecated)].

Feature staging for rustc authors

For people hacking on rust itself there are a number of things to know.
Firstly, the in-tree design is not intended to make your life easier -
lots of stuff has to be tagged with metadata, but only incrementally
more so than before.

Crates that participate in feature staging (i.e. every crate in-tree
and no others) need to opt in. This requires the #[staged_api]
attribute, which itself requires the staged_api feature. Being
'staged_api' is what tells rustc it needs to interpret the 'unstable',
'stable', and 'deprecated' attributes (while leaving the possibility
of also using those names for non-staged-api purposes later).

Therefore every crate in-tree must say:

#![feature(staged_api)]
#![staged_api]

Nearly every crate that is not part of the standard facade should
additionally include the crate header #![unstable(feature = "rustc_private")], which says 'this crate is not for public
consumption'.

When adding a new public API to the standard library or to a crate in
the standard facade, you need to tag it unstable and give it a feature
name:

#[unstable(feature = "new_hotness")]
fn new_hotness() { }

Increasingly, this feature name should come out of the RFC process,
and adding APIs without knowing what feature it is part of will be an
indication that the development process is not proceeding in order.

Unstable features inherit lexically, so you often only need a
single one.

When a feature becomes stable you change the attribute from 'unstable'
to 'stable' and add the 'since' attribute:

#[stable(feature = "new_hotness", since = "1.1.0")]
fn new_hotness() { }

'since' is the version in which that feature will hit the stable
release channel and at the time of promotion should be equal to the
value CFG_RELEASE_NUM in main.mk.

The stable attribute does not inherit lexically, so most items need
to be tagged explicitly, including trait methods and inherent methods.

No single feature can be both unstable and stable, so if only part of
an unstable feature needs to be promoted then it must be split into
two features. All the 'since' attributes must agree for a given
feature. A new tidy script enforces both these and can be run with
'make tidy-features'.

Deprecating an API can be done with #[deprecated(since = "1.1.0")],
the same as before, but now requiring 'since'. Since both unstable and
stable libraries can be deprecated, all deprecated attributes must be
paired with either 'unstable' or 'stable'.

5 Likes

Will the compiler warn when your code contains a feature attribute that has been made stable, in order to make it easy to prune unneeded feature attributes and maintain the property "stable crates don't contain #![feature]"?

@bstrie Yes! Declaring a feature that is either stable or unused will trip the 'unused_feature' lint, which is warn by default.

I'm getting unused feature errors. It would be dumb if stabilization meant that my working code would actually stop compiling because it became more stable (when libstd APIs stabilize).

error: unused or unknown feature, #[deny(unused_features)] on by default

1 Like

@bluss Ah, interesting. I'm inclined to agree with you that deny(unused_features) by default is too punitive here.

Note that to compile with the beta or stable compilers the feature attribute can't be written at all, so it is expected that crates have to take a transitional step of removing all feature attributes before being compatible with the stable compiler.

Here's an issue unused_features warn by default? · Issue #21798 · rust-lang/rust · GitHub