devtake.dev

Rust 1.95 lands with if-let guards and a macro that kills cfg-if

Rust 1.95.0 stabilized cfg_select!, if-let guards in match arms, and a pile of new Vec and atomic APIs. Here's what's actually worth upgrading for.

Editorial Team · · 4 min read · 3 sources
Rust programming language logo on an orange gradient background
Image: blog.rust-lang.org · Source

Rust 1.95.0 shipped on April 16, and the headline feature is cfg_select!. It’s a compile-time macro that behaves like a match over cfg predicates, and it effectively ends the reign of the third-party cfg-if crate that sits in something like 40,000 dependency trees on crates.io.

The release is light on pageantry and heavy on quality-of-life fixes. If you spend your day writing #[cfg(target_os = "linux")] gates, you’ll notice this one right away.

What actually changed

cfg_select! is the headline. Instead of sprawling cfg attribute chains or reaching for an external crate, you can now write a compile-time match that expands to the first arm whose predicate is true. The official blog post shows it picking between platform-specific implementations with a fallback arm. It’s the kind of feature that looks small on paper and changes how you structure cross-platform code once you start using it.

The second-biggest landing is if-let guards inside match arms. Building on the let chains that stabilized in 1.88, you can now write patterns like Some(x) if let Ok(y) = compute(x) => { ... }. There’s one sharp edge worth reading twice: the compiler does not consider patterns matched inside if let guards as part of exhaustiveness checking. If you’ve been writing manual workarounds with nested matches, that restriction probably won’t bite you, but it’s the kind of thing that will show up in a Clippy lint before long.

The push_mut / insert_mut family is the sleeper improvement. Vec::push_mut, Vec::insert_mut, VecDeque::push_front_mut, VecDeque::push_back_mut, VecDeque::insert_mut, and LinkedList equivalents all return a mutable reference to the element you just inserted. That kills the let i = v.len(); v.push(x); &mut v[i] pattern you’ve seen (and probably written) a hundred times. Small, useful, the sort of thing Rust takes its time on and gets right.

The rest of the stabilized list

  • Atomic update and try_update on AtomicBool, AtomicPtr, AtomicIsize, and AtomicUsize. A CAS loop in two lines instead of ten.
  • MaybeUninit<[T; N]> now converts from [MaybeUninit<T>; N] and implements AsRef / AsMut both ways. One fewer reason to reach for unsafe in array initialization code.
  • core::range with RangeInclusive and RangeInclusiveIter, the continued cleanup of the range-type split that’s been ongoing since 1.80.
  • core::hint::cold_path() for hot-loop branch hinting without resorting to intrinsics.
  • bool: TryFrom<{integer}> for every integer width. A tiny ergonomic win that used to require a helper.
  • Const-stable fmt::from_fn and ControlFlow::is_break / is_continue.
  • Pointer unchecked derefs via as_ref_unchecked / as_mut_unchecked. Unsafe, narrow, but now stable.

One thing to watch on upgrade day

Custom target spec JSON files are no longer supported on stable. You could already only build the standard library with them on nightly, so this mostly cleans up a confusing corner of the tooling, but if you maintain an embedded or bare-metal toolchain that relied on custom --target=my-target.json invocations from stable, your CI will break. The release notes flag this as the one breaking change worth reading twice.

LWN’s short announcement doesn’t add commentary, just points to the official notes. No drama, no controversy. Rust releases have been unusually boring lately, and that’s probably a compliment.

What this means for you

If you ship a library that supports more than one platform, cfg_select! is worth refactoring to on the next quiet afternoon. The diff makes gnarly cfg chains readable again, and it lets you drop cfg-if from your Cargo.toml, which is one fewer transitive dependency to audit. If you write high-performance or unsafe-heavy code, the new atomic update methods and the MaybeUninit conversions will tighten your hot paths. Everyone else: rustup update, verify your CI is green, move on. The breaking change around JSON target specs won’t affect you unless you already knew what a JSON target spec is, in which case you know what to do. For a closer look at where Rust’s ecosystem is pulling the rest of the stack, this week’s trending-dev digest has a few projects that rely heavily on the language’s stabilization cadence.

Sources