Rust 1.95: cfg_select! joins the standard library and match gets if-let guards

Rust 1.95.0 was released on April 16. If you already have Rust installed through rustup, you upgrade with rustup update stable. It is not a headline-grabbing release, but it brings two language changes that will show up in everyday code and one removal that deserves attention from anyone working with custom targets.

cfg_select!: the cfg-if that became standard

Any Rust project that needs to behave differently per platform ends up reaching for the cfg-if crate. Rust 1.95 stabilizes the cfg_select! macro, which, according to the announcement, works roughly like a compile-time match on cfg predicates. It serves the same purpose as cfg-if, with different syntax, and expands to the right-hand side of the first arm whose predicate is true.

The example from the official post shows both uses, for defining items and as an expression:

cfg_select! {
    unix => {
        fn foo() { /* unix specific functionality */ }
    }
    target_pointer_width = "32" => {
        fn foo() { /* non-unix, 32-bit functionality */ }
    }
    _ => {
        fn foo() { /* fallback implementation */ }
    }
}

let is_windows_str = cfg_select! {
    windows => "windows",
    _ => "not windows",
};

The _ arm acts as the fallback, which makes the default behavior explicit. I especially like the expression form: picking a value per platform in one line, without duplicating #[cfg] blocks, makes code much more readable. It is one less dependency for many crates.

if-let guards in match

Rust 1.88 stabilized let chains. Version 1.95 brings that capability into match expressions: a guard can now use if let to perform a second pattern match. The example from the announcement:

match value {
    Some(x) if let Ok(y) = compute(x) => {
        // Both `x` and `y` are available here
        println!("{}, {}", x, y);
    }
    _ => {}
}

Before, this pattern required nesting an if let inside the arm or restructuring the logic. The important caveat, highlighted in the post itself: the compiler does not count patterns in if let guards toward match exhaustiveness, just as with regular if guards. In other words, you still need the catch-all arm.

End of stable support for JSON target specs

Rust 1.95 removes stable support for passing a custom target specification to rustc. According to the team, anyone on a fully stable toolchain should not be affected, because building the standard library, even just core, already required nightly-only features. The team is collecting use cases on a tracking issue to decide whether some form of this feature should be stabilized in the future. If you work on embedded systems or kernels and depend on this, it is worth adding your case there.

Stabilized APIs

The list is long. The highlights, for me:

  • bool: TryFrom for integers, an explicit and fallible conversion.
  • update and try_update methods on AtomicPtr, AtomicBool and the integer atomics, which simplify compare-and-swap loops.
  • The core::range module, with RangeInclusive and RangeInclusiveIter.
  • core::hint::cold_path, to tell the compiler a path is rarely taken.
  • Vec::push_mut and Vec::insert_mut, plus push_front_mut, push_back_mut and insert_mut on VecDeque, and equivalents on LinkedList, which return a mutable reference to the inserted element.
  • From, AsRef and AsMut conversions between arrays of MaybeUninit and MaybeUninit of arrays, and similar conversions for Cell.
  • Layout::dangling_ptr, Layout::repeat, Layout::repeat_packed and Layout::extend_packed.

In addition, fmt::from_fn and the ControlFlow::is_break and ControlFlow::is_continue methods are now usable in const contexts.

My take

Rust 1.95 is a good example of how the language evolves: absorbing patterns the community has already validated in crates, like cfg-if, and closing ergonomic gaps, like if let guards. Neither change is revolutionary, but both remove ceremonial code everyone writes.

The push_mut methods look small, but they solve an old annoyance: inserting an element and immediately needing a reference to it. Removing JSON target specs from stable is the only change that might cause friction, and even then for a narrow audience that, in practice, already depended on nightly. A smooth upgrade, and a recommended one.

Sources