Stability attributes

This section is about the stability attributes and schemes that allow stable APIs to use unstable APIs internally in the rustc standard library.

NOTE: this section is for library features, not language features. For instructions on stabilizing a language feature see Stabilizing Features.

unstable

The #[unstable(feature = "foo", issue = "1234", reason = "lorem ipsum")] attribute explicitly marks an item as unstable. Items that are marked as "unstable" cannot be used without a corresponding #![feature] attribute on the crate, even on a nightly compiler. This restriction only applies across crate boundaries, unstable items may be used within the crate that defines them.

The issue field specifies the associated GitHub issue number. This field is required and all unstable features should have an associated tracking issue. In rare cases where there is no sensible value issue = "none" is used.

The unstable attribute infects all sub-items, where the attribute doesn't have to be reapplied. So if you apply this to a module, all items in the module will be unstable.

You can make specific sub-items stable by using the #[stable] attribute on them. The stability scheme works similarly to how pub works. You can have public functions of nonpublic modules and you can have stable functions in unstable modules or vice versa.

Previously, due to a rustc bug, stable items inside unstable modules were available to stable code in that location. As of September 2024, items with accidentally stabilized paths are marked with the #[rustc_allowed_through_unstable_modules] attribute to prevent code dependent on those paths from breaking.

The unstable attribute may also have the soft value, which makes it a future-incompatible deny-by-default lint instead of a hard error. This is used by the bench attribute which was accidentally accepted in the past. This prevents breaking dependencies by leveraging Cargo's lint capping.

stable

The #[stable(feature = "foo", since = "1.420.69")] attribute explicitly marks an item as stabilized. Note that stable functions may use unstable things in their body.

rustc_const_unstable

The #[rustc_const_unstable(feature = "foo", issue = "1234", reason = "lorem ipsum")] has the same interface as the unstable attribute. It is used to mark const fn as having their constness be unstable. This is only needed in rare cases:

  • If a const fn makes use of unstable language features or intrinsics. (The compiler will tell you to add the attribute if you run into this.)
  • If a const fn is #[stable] but not yet intended to be const-stable.
  • To change the feature gate that is required to call a const-unstable intrinsic.

Const-stability differs from regular stability in that it is recursive: a #[rustc_const_unstable(...)] function cannot even be indirectly called from stable code. This is to avoid accidentally leaking unstable compiler implementation artifacts to stable code or locking us into the accidental quirks of an incomplete implementation. See the rustc_const_stable_indirect and rustc_allow_const_fn_unstable attributes below for how to fine-tune this check.

rustc_const_stable

The #[rustc_const_stable(feature = "foo", since = "1.420.69")] attribute explicitly marks a const fn as having its constness be stable.

rustc_const_stable_indirect

The #[rustc_const_stable_indirect] attribute can be added to a #[rustc_const_unstable(...)] function to make it callable from #[rustc_const_stable(...)] functions. This indicates that the function is ready for stable in terms of its implementation (i.e., it doesn't use any unstable compiler features); the only reason it is not const-stable yet are API concerns.

This should also be added to lang items for which const-calls are synthesized in the compiler, to ensure those calls do not bypass recursive const stability rules.

rustc_intrinsic_const_stable_indirect

On an intrinsic, this attribute marks the intrinsic as "ready to be used by public stable functions". If the intrinsic has a rustc_const_unstable attribute, it should be removed. Adding this attribute to an intrinsic requires t-lang and wg-const-eval approval!

rustc_default_body_unstable

The #[rustc_default_body_unstable(feature = "foo", issue = "1234", reason = "lorem ipsum")] attribute has the same interface as the unstable attribute. It is used to mark the default implementation for an item within a trait as unstable. A trait with a default-body-unstable item can be implemented stably by providing an explicit body for any such item, or the default body can be used by enabling its corresponding #![feature].

Stabilizing a library feature

To stabilize a feature, follow these steps:

  1. Ask a @T-libs-api member to start an FCP on the tracking issue and wait for the FCP to complete (with disposition-merge).
  2. Change #[unstable(...)] to #[stable(since = "CURRENT_RUSTC_VERSION")].
  3. Remove #![feature(...)] from any test or doc-test for this API. If the feature is used in the compiler or tools, remove it from there as well.
  4. If this is a const fn, add #[rustc_const_stable(since = "CURRENT_RUSTC_VERSION")]. Alternatively, if this is not supposed to be const-stabilized yet, add #[rustc_const_unstable(...)] for some new feature gate (with a new tracking issue).
  5. Open a PR against rust-lang/rust.
    • Add the appropriate labels: @rustbot modify labels: +T-libs-api.
    • Link to the tracking issue and say "Closes #XXXXX".

You can see an example of stabilizing a feature with tracking issue #81656 with FCP and the associated implementation PR #84642.

allow_internal_unstable

Macros and compiler desugarings expose their bodies to the call site. To work around not being able to use unstable things in the standard library's macros, there's the #[allow_internal_unstable(feature1, feature2)] attribute that allows the given features to be used in stable macros.

Note that if a macro is used in const context and generates a call to a #[rustc_const_unstable(...)] function, that will still be rejected even with allow_internal_unstable. Add #[rustc_const_stable_indirect] to the function to ensure the macro cannot accidentally bypass the recursive const stability checks.

rustc_allow_const_fn_unstable

As explained above, no unstable const features are allowed inside stable const fn, not even indirectly.

However, sometimes we do know that a feature will get stabilized, just not when, or there is a stable (but e.g. runtime-slow) workaround, so we could always fall back to some stable version if we scrapped the unstable feature. In those cases, the [rustc_allow_const_fn_unstable(feature1, feature2)] attribute can be used to allow some unstable features in the body of a stable (or indirectly stable) const fn.

You also need to take care to uphold the const fn invariant that calling it at runtime and compile-time needs to behave the same (see also this blog post). This means that you may not create a const fn that e.g. transmutes a memory address to an integer, because the addresses of things are nondeterministic and often unknown at compile-time.

Always ping @rust-lang/wg-const-eval if you are adding more rustc_allow_const_fn_unstable attributes to any const fn.

staged_api

Any crate that uses the stable or unstable attributes must include the #![feature(staged_api)] attribute on the crate.

deprecated

Deprecations in the standard library are nearly identical to deprecations in user code. When #[deprecated] is used on an item, it must also have a stable or unstable attribute.

deprecated has the following form:

#[deprecated(
    since = "1.38.0",
    note = "explanation for deprecation",
    suggestion = "other_function"
)]

The suggestion field is optional. If given, it should be a string that can be used as a machine-applicable suggestion to correct the warning. This is typically used when the identifier is renamed, but no other significant changes are necessary. When the suggestion field is used, you need to have #![feature(deprecated_suggestion)] at the crate root.

Another difference from user code is that the since field is actually checked against the current version of rustc. If since is in a future version, then the deprecated_in_future lint is triggered which is default allow, but most of the standard library raises it to a warning with #![warn(deprecated_in_future)].