Rust 1.95.0 ships with two language features worth understanding in depth: cfg_select!, which lands as a first-class replacement for the cfg-if crate that has lived in almost every Rust project’s dependency tree for years, and if-let guards in match expressions, which complete the ergonomic story that let chains began in 1.88.
The cfg-if Problem
To understand why cfg_select! matters, you need to appreciate how much of the Rust ecosystem has depended on the cfg-if crate. Written by Alex Crichton, it solves a real awkwardness in Rust’s conditional compilation story.
Rust’s #[cfg(...)] attributes are powerful but compositional only to a point. When you need two platform-specific implementations of a single item, the standard approach is to write two items with complementary conditions:
#[cfg(unix)]
fn platform_name() -> &'static str { "unix" }
#[cfg(not(unix))]
fn platform_name() -> &'static str { "other" }
This works for two cases; add a third and you are either duplicating condition logic or writing nested #[cfg(all(not(unix), not(windows)))] expressions that become unreadable fast. cfg-if solved this with an if/else if/else macro:
cfg_if::cfg_if! {
if #[cfg(unix)] {
fn platform_name() -> &'static str { "unix" }
} else if #[cfg(windows)] {
fn platform_name() -> &'static str { "windows" }
} else {
fn platform_name() -> &'static str { "other" }
}
}
This works, and the crate became ubiquitous. It spread across the ecosystem as a transitive dependency of low-level crates, meaning virtually every Rust project pulled it in whether or not they used it directly. But it required an external dependency, its syntax diverged from the rest of Rust (the if #[cfg(...)] form looks like nothing else in the language), and it could not be used as an expression.
cfg_select! in 1.95
The Rust 1.95.0 release stabilizes cfg_select! as a standard library macro that takes the concept from cfg-if and redesigns it around Rust’s match syntax. Arms use => and the wildcard is _, consistent with pattern matching everywhere else in the language:
cfg_select! {
unix => {
fn foo() { /* unix specific functionality */ }
}
target_pointer_width = "32" => {
fn foo() { /* non-unix, 32-bit functionality */ }
}
_ => {
fn foo() { /* fallback implementation */ }
}
}
Arms are evaluated in order. The first arm whose configuration predicate is true wins, similar to how match works at runtime. The _ wildcard makes the fallback case unambiguous and visually consistent with the rest of the language.
The more significant addition is the expression form. Because cfg_select! can expand to a value, you can use it anywhere an expression is valid:
let is_windows_str = cfg_select! {
windows => "windows",
_ => "not windows",
};
cfg-if has no equivalent. Its macro expands to items, not expressions, so you cannot use it to choose between two constant values inline. You had to define a function or constant with separate #[cfg] attributes and then reference that. The expression form in cfg_select! removes that indirection entirely, and opens up use cases like inline platform-conditional constant initialization that were previously awkward.
Why the Syntax Diverges from cfg-if
The cfg_select! design uses condition => body pairs with _ as the default rather than if #[cfg(...)] / else if #[cfg(...)] / else. This is deliberate alignment with Rust’s match syntax and makes the macro immediately readable to anyone who knows the language. The if #[cfg(...)] form in cfg-if was always a quirk; it read intuitively enough, but it is not how anything else in Rust is structured.
The expression form also aligns with Rust’s broader philosophy that most constructs should be expressions. if/else is an expression. match is an expression. loop is an expression. cfg_select! now joins that set, making conditional compilation feel like a first-class participant in the language rather than a preprocessing step grafted on top of it.
For crates that list cfg-if as a direct dependency solely for its macro, the migration is straightforward: swap the syntax, remove the entry from Cargo.toml. For crates where cfg-if arrives only as a transitive dependency, the effect will be gradual as upstream crates migrate, but the trajectory is clear. Over time, one of the most prevalent crates in the ecosystem’s dependency graph becomes optional infrastructure rather than a required workaround.
if-let Guards in Match
The second notable stabilization is if-let guards in match expressions. To understand what this adds, it helps to trace the trajectory of let chains in Rust.
RFC 2497 proposed let chains, which allow chaining let patterns with && in if and while conditions:
if let Some(user) = get_user() && let Some(profile) = user.profile() {
// both bindings available here
}
This was stabilized in Rust 1.88 and immediately reduced nested if let ladders in real codebases. But the same capability was not available in match guards. A match guard is the if condition clause that appears after a match arm’s pattern:
match event {
Event::Message(msg) if msg.len() > 100 => { /* long message */ }
_ => {}
}
Before 1.95, that if clause could contain any boolean expression but could not introduce new bindings via let. You could call a function returning a bool, but you could not simultaneously bind a new variable and condition the arm on whether that binding succeeded. If you needed a value from some lookup to both exist and be available in the arm body, the standard approach was to compute it inside the arm, which meant the arm always fired on pattern match and the fallback logic lived inside rather than at the dispatch level.
With if-let guards, you can now write:
match event {
Event::Message(msg) if let Some(user) = registry.find_user(msg.author) => {
process(msg, user)
}
_ => {}
}
The let inside the guard binds user, and that binding is available in the arm body. The arm only fires if the pattern matches and the guard’s let pattern succeeds. Other arms, including the wildcard, see the cases where find_user returned None.
This closes a real inconsistency. The absence of let in match guards was an implementation artifact, not a principled design choice. Noting that if conditions support let chains while match guards do not required an explanation rooted in implementation history rather than language semantics. Rust 1.95 removes the need for that explanation.
The use case that comes up most often is dispatching on a tagged value that requires a secondary lookup before you can fully handle an arm. This pattern appears in event-driven code, state machines, and anywhere you are routing on a discriminant but need context resolved per-arm. Previously, the cleanest option was a nested match or if let inside the arm body after the outer match had already committed. With if-let guards, the check lives at the guard level, where it semantically belongs.
The Larger Pattern
Both features in 1.95 represent the same kind of work: closing gaps that the ecosystem had filled with workarounds. cfg-if was a good workaround for conditional compilation, but it required a dependency and offered a syntax that did not integrate with the rest of the language. If-let guards in match expressions were absent due to implementation complexity, not because they were undesirable.
Rust has always moved deliberately on features that touch core language semantics, and both of these went through years of design iteration before stabilization. The results are solutions that behave as native features rather than extensions, and that is the standard Rust has set for itself. The bar is high, and 1.95 clears it on both counts.
To update: rustup update stable.