Stable vs. Unstable Features
Rust separates language syntax, standard library APIs, and compiler flags into stable features—which carry a permanent backward-compatibility guarantee (#[stable(feature = "...", since = "...")])—and unstable features (#[unstable(feature = "...", issue = "...")]), which are under active design iteration and can only be enabled on the nightly compiler channel via an explicit #![feature(...)] crate-level attribute.
+-------------------------------------------------------------------+
| Rust Compiler Codebase (`rustc`) |
| |
| Stable Features (`#[stable(..., since = "1.85.0")]`) |
| ---> Available on Stable, Beta, AND Nightly compilers! |
| ---> Guaranteed never to break backward compatibility! |
| |
| Unstable Features (`#[unstable(..., issue = "12345")]`) |
| ---> Blocked by feature gate on Stable & Beta (`error[E0554]`) |
| ---> Unlocked ONLY on Nightly via `#![feature(name)]`! |
+-------------------------------------------------------------------+
Comparing Stable, Beta, and Nightly Feature Access
| Compiler Channel | Accepts #![feature(...)]? |
Unstable CLI Flags (-Z ...)? |
Backward Compatibility Guarantee |
|---|---|---|---|
Stable (rustup default stable) |
No (error[E0554]) |
No (Requires -C ... stable flags) |
Strong SemVer stability: valid stable code continues to compile across future 1.x releases. |
Beta (rustup +beta ...) |
No | No | Pre-release candidate for the upcoming 6-week Stable release (used in CI to catch regressions early). |
Nightly (rustup +nightly ...) |
Yes | Yes (-Zsanitizer=..., -Zbuild-std) |
None for unstable features: #![feature(...)] APIs may be renamed, modified, or removed on any nightly build. |
How the Feature Gate Rejects #![feature(...)] on Stable Rust
Why does the stable compiler explicitly forbid #![feature(...)] even though the implementation code for experimental features already sits inside the same rustc binary?
If stable compilers allowed #![feature(...)], library crates on crates.io would begin depending on half-finished experimental features, effectively freezing those experimental designs before their edge cases were solved! Blocking #![feature(...)] on stable compilers (error[E0554]) protects the ecosystem from accidental lock-in to unstable APIs:
// ❌ does not compile
#![feature(never_type)]
fn main() {
println!("Attempting to use #![feature(...)] on a stable compiler");
}
error[E0554]: `#![feature]` may not be used on the stable release channel
--> src/main.rs:1:1
|
1 | #![feature(never_type)]
| ^^^^^^^^^^^^^^^^^^^^^^^
Conditional Nightly Acceleration (cfg_attr)
When a library wants to compile 100% on stable Rust by default while optionally enabling a nightly-only optimization or documentation feature (such as doc_auto_cfg on docs.rs or portable SIMD via an opt-in Cargo feature nightly), combine #![cfg_attr(...)] with a Cargo feature flag:
// Compiles cleanly on Stable Rust!
// Only enables `#![feature(...)]` when `--features nightly-optimizations` is passed on a nightly compiler:
#![cfg_attr(feature = "nightly-optimizations", feature(test))]
pub fn compute_popcount_sum(data: &[u64]) -> u32 {
data.iter().map(|&x| x.count_ones()).sum()
}
fn main() {
let values = [0b1011u64, 0b1111, 0b0001];
println!("Total set bits on stable Rust: {}", compute_popcount_sum(&values));
}
Total set bits on stable Rust: 8