Stable Language Evolution

Feature Stabilization

Feature stabilization is the formal engineering and governance process by which an experimental feature (#[unstable]) transitions into a permanently supported part of stable Rust (#[stable(feature = "...", since = "1.XX.0")]). Once stabilized, the #![feature(...)] gate is no longer needed (and emits a harmless stable_features warning if left behind).

Lifecycle of a Rust Feature:

  [ 1. RFC / ACP Approved ] (Design agreed upon by Lang / Libs-API Team)
              |
              v
  [ 2. Nightly Implementation ] (`#[unstable(feature = "foo", issue = "NNNNN")]`)
              |
              v
  [ 3. Nightly Testing & Iteration ] (Edge cases fixed, Miri/Crater verification)
              |
              v
  [ 4. Stabilization Report & FCP ] (10-day Final Comment Period with Team Sign-Off)
              |
              v
  [ 5. Stabilized (`#[stable(feature = "foo", since = "1.85.0")]`) ]
        Rides Train: Nightly ---> Beta (6 weeks) ---> Stable Release!

Partial Stabilization: Stabilizing the Core Subset First

Rather than holding back an entire large feature for years because 5% of its edge cases are still unresolved, Rust teams frequently practice partial (incremental) stabilization:

  • Const Generics: Stabilized const N: usize integer/char/bool parameters in Rust 1.51 while leaving complex generic const expressions unstable for further work.
  • Async Functions in Traits (AFIT): Stabilized async fn in traits and Return-Position impl Trait in traits (RPITIT) in Rust 1.75 while continuing work on dynamic trait-object (dyn Trait) adapters.
  • Strict Provenance & Raw Pointers: Stabilized &raw const / &raw mut in Rust 1.82 and .addr() / .with_addr() / .map_addr() in Rust 1.84.

How the Compiler Tracks Stability Attributes Internally

Inside the standard library source code (core, alloc, std), every public module, struct, trait, method, and impl block carries a stability attribute checked by the compiler:

Internal Std Attribute Meaning
#[unstable(feature = "foo", issue = "12345")] Requires #![feature(foo)] on nightly; links compiler error messages directly to GitHub tracking issue #12345.
#[stable(feature = "foo", since = "1.80.0")] Available on all compilers since Rust 1.80.0.
#[rustc_const_unstable(feature = "...", issue = "...")] The function itself is stable at runtime, but calling it inside a const context is still unstable!
#[rustc_const_stable(feature = "...", since = "1.85.0")] Calling the function in both runtime and const contexts (const fn) is permanently stable!

Notice the distinction between runtime stability and const stability: a standard library function often stabilizes for normal runtime calls first (#[stable]), and later gains const fn stability (#[rustc_const_stable]) once its compile-time evaluation semantics are proven sound!

/// Demonstrates recently stabilized standard library features working together:
/// - `Option::is_none_or` (stabilized in Rust 1.82)
/// - `slice::split_at_checked` (stabilized in Rust 1.80)
/// - `const` floating-point arithmetic in `const fn` (stabilized in Rust 1.82)
pub const fn fahrenheit_to_celsius(f: f64) -> f64 {
    (f - 32.0) * (5.0 / 9.0)
}

pub fn validate_header_prefix(packet: &[u8]) -> bool {
    let Some((header, payload)) = packet.split_at_checked(4) else {
        return false;
    };
    header == b"RUST" && payload.first().is_none_or(|&b| b != 0)
}

fn main() {
    const BOILING_C: f64 = fahrenheit_to_celsius(212.0);
    assert!((BOILING_C - 100.0).abs() < 1e-9);

    assert!(validate_header_prefix(b"RUST\x01data"));
    assert!(!validate_header_prefix(b"RUST\x00data"));
    assert!(!validate_header_prefix(b"SHRT"));

    println!("Const boiling point: {BOILING_C:.1}C, header valid: true");
}
Const boiling point: 100.0C, header valid: true

Crater Runs Before Stabilization

Before stabilizing any nontrivial language or trait-inference change, the Rust Release and Compiler teams run Crater—an automated distributed build system that compiles and runs cargo test across every public crate on crates.io and public GitHub repositories (over 150,000+ crates) comparing the baseline compiler against the candidate PR to ensure existing stable code is not broken by new inference ambiguities or method name collisions.