Rust 1.95 Lands cfg_select! and if-let Match Guards, Closing Gaps That Crates Have Filled for Years
Source: rust
Rust 1.95.0 shipped on April 16, 2026, and the two headline features share something in common beyond syntax: both address problems that the Rust community had already solved through crates and workarounds, and the language is finally absorbing those solutions natively. That matters not just for convenience, but for what it says about how Rust grows.
cfg_select! and the cfg-if Problem
Conditional compilation in Rust has always been possible with the #[cfg(...)] attribute. It works fine for simple cases:
#[cfg(unix)]
fn foo() { /* unix */ }
#[cfg(not(unix))]
fn foo() { /* fallback */ }
Once you have more than two branches, though, this gets unwieldy fast. There is no else-if equivalent in the attribute syntax, so you end up with increasingly complex not(any(...)) predicates to express what should be a straightforward set of mutually exclusive conditions. For years, the community solution was cfg-if, created by Alex Crichton and maintained by dtolnay, which lets you write:
cfg_if::cfg_if! {
if #[cfg(unix)] {
fn foo() { /* unix */ }
} else if #[cfg(target_pointer_width = "32")] {
fn foo() { /* 32-bit non-unix */ }
} else {
fn foo() { /* fallback */ }
}
}
That crate has been downloaded billions of times. It shows up in virtually every cross-platform Rust project and is a transitive dependency of an enormous portion of the crates.io graph. The fact that it existed and thrived for so long is evidence both of how much the community needed this pattern and of how willing Rust developers are to reach for a crate when the language leaves a gap.
Rust 1.95 closes that gap with cfg_select!, which uses match-arm syntax instead of the if-else style:
cfg_select! {
unix => {
fn foo() { /* unix specific functionality */ }
}
target_pointer_width = "32" => {
fn foo() { /* non-unix, 32-bit functionality */ }
}
_ => {
fn foo() { /* fallback implementation */ }
}
}
The _ wildcard arm is the fallback, the same way it works in a regular match. The macro expands to whichever arm’s configuration predicate is true first, making it strictly sequential. Only one arm fires. If no arm matches and there is no wildcard, it is a compile error.
The syntax difference from cfg-if is not trivial. Match-arm notation is native Rust idiom in a way that the if-else style in cfg-if never quite was, because cfg-if had to use if #[cfg(...)] as a workaround for the actual attribute syntax. The cfg_select! notation reads more cleanly as a decision table, which is what it is.
Beyond aesthetics, there is a practical advantage in using expression context. You can write:
let is_windows_str = cfg_select! {
windows => "windows",
_ => "not windows",
};
With cfg-if, this kind of expression-level conditional was more awkward because the macro was designed primarily around item-level code generation. cfg_select! works in both expression and item position, which covers a wider range of real-world use cases without requiring a workaround.
For anyone writing code that targets multiple platforms, including cross-platform tooling, embedded targets with different pointer widths, or OS-specific system call wrappers, cfg_select! removes a dependency without removing capability. The cfg-if crate will not disappear, and code using it does not need to migrate. But new code now has a native option.
if-let Guards in Match: The Final Piece of a Longer Story
The other major stabilization in 1.95 is if-let guards in match expressions, and to understand why this matters you need to go back to Rust 1.88, which stabilized let chains.
Let chains allow you to combine multiple let pattern bindings with && inside if conditions:
if let Some(user) = get_user() && let Some(email) = user.email {
send_message(email);
}
Prior to let chains, you had to nest these, which added indentation for every additional binding. Let chains flatten that into a single condition that short-circuits correctly and makes all bindings available in the body.
Match guards already existed before 1.95. You could write:
match value {
Some(x) if x > 0 => println!("positive: {x}"),
_ => {}
}
The guard after if accepts any boolean expression. What it did not accept, until 1.95, was a let pattern binding. So you could not do this:
match event {
Event::Message(msg) if let Ok(parsed) = parse_command(&msg.content) => {
handle_command(parsed);
}
_ => {}
}
Before this release, you had to either nest a secondary match or if-let inside the match arm body, or restructure the logic entirely. That inconsistency was always a rough edge: let chains worked in if, but the same pattern binding capability was unavailable in match guards.
1.95 closes this by allowing if let in match guards, which means you can bind new variables from complex expressions as part of the guard condition. The bindings are scoped to that arm and available in the arm body. Combined with let chains, you can also chain multiple let bindings in a guard:
match event {
Event::Message(msg)
if let Ok(cmd) = parse_command(&msg.content)
&& let Some(handler) = get_handler(&cmd) =>
{
handler.execute(cmd);
}
_ => {}
}
This kind of pattern is common in event-driven code. A Discord bot, for example, might receive raw messages and want to pattern-match on the event type while also parsing and validating the command in the guard, avoiding a deeply nested structure in the arm body. Previously that required either a helper function or nested control flow inside the arm.
The underlying design work here connects back to RFC 2497, which proposed let chains, and the subsequent work to extend the same semantics into match. Both features required careful handling of variable scope and borrow checker interaction, because bindings introduced in a guard must be live in the arm body but not in other arms.
What This Release Signals About Rust’s Growth Process
Rust’s relationship with its own ecosystem has always been deliberate. The language explicitly encourages solving problems in crates first, then standardizing what proves useful. cfg-if is a textbook example: it identified a real gap, became ubiquitous, and now the language has absorbed the capability in a more idiomatic form.
This process has costs. The gap between community solution and language feature can span years. Developers learn cfg-if syntax, build muscle memory around it, and then face a moment where there is a new preferred way that will not be universally adopted overnight. But the benefit of this model is that features arrive with substantial real-world validation behind them. cfg_select! was not designed speculatively; it was designed knowing that the pattern had already proven its value.
The match guard work is similar in spirit. Match guards with simple boolean conditions have been stable for a very long time, but extending them to accept let bindings required the groundwork from let chains and careful work on the desugaring. The feature was not shipped until the semantics were well understood.
1.95 is not a landmark release in terms of scale, but both features have the quality of feeling long overdue once you see them. Cross-platform Rust code will gradually shift toward cfg_select! where the expression-level syntax is cleaner, and match-heavy code will benefit from guards that can do real work without pulling in separate functions. These are the kinds of improvements that compound: individually modest, collectively significant.