The Rust team released 1.95.0 on April 16, 2026. Two features in this release stand out not for being technically ambitious but for being genuinely overdue: the built-in cfg_select! macro and if-let guards in match expressions. Both address patterns that Rust programmers have been working around for years, one through a near-universal crate dependency, the other through awkward structural nesting the language technically required.
The cfg-if Gap That cfg_select! Fills
Conditional compilation in Rust relies on #[cfg(...)] attributes, which work cleanly on individual items but become unwieldy when you need to select between multiple platform-specific implementations. The conventional solution has been the cfg-if crate, one of the most widely depended-on crates in the entire ecosystem, originally created by Alex Crichton during his time at Mozilla. It sits in the dependency tree of virtually every non-trivial Rust project.
cfg-if’s macro provides if/else chains over cfg predicates:
cfg_if::cfg_if! {
if #[cfg(unix)] {
fn get_home_dir() -> std::path::PathBuf {
std::path::PathBuf::from(std::env::var("HOME").unwrap())
}
} else if #[cfg(windows)] {
fn get_home_dir() -> std::path::PathBuf {
std::path::PathBuf::from(std::env::var("USERPROFILE").unwrap())
}
} else {
fn get_home_dir() -> std::path::PathBuf {
std::path::PathBuf::from("/")
}
}
}
This works, but it demands an external dependency for something that is a core language concern. The if #[cfg(...)] syntax inside the macro is also slightly disorienting; you are inside the macro’s domain rather than the language’s, and the cfg predicate appears as an attribute on the if keyword rather than as a direct expression.
Rust 1.95 stabilizes cfg_select! as a standard library macro with match-like syntax:
cfg_select! {
unix => {
fn get_home_dir() -> std::path::PathBuf {
std::path::PathBuf::from(std::env::var("HOME").unwrap())
}
}
windows => {
fn get_home_dir() -> std::path::PathBuf {
std::path::PathBuf::from(std::env::var("USERPROFILE").unwrap())
}
}
_ => {
fn get_home_dir() -> std::path::PathBuf {
std::path::PathBuf::from("/")
}
}
}
The mechanics are the same: the compiler evaluates each cfg predicate in order and expands the first arm whose predicate is true. The fallback _ arm covers every case not matched above. But the syntax is more coherent. Predicates appear directly as match arms rather than wrapped in #[cfg(...)] attributes, which is consistent with how cfg predicates appear elsewhere in the language.
The more significant capability is that cfg_select! works as an expression. cfg-if never supported this; it could only select between item definitions.
// Valid in 1.95, not achievable with cfg-if
let arch_name = cfg_select! {
target_arch = "x86_64" => "x86_64",
target_arch = "aarch64" => "aarch64",
target_arch = "riscv64" => "riscv64",
_ => "unknown",
};
const MAX_ALIGN: usize = cfg_select! {
target_pointer_width = "64" => 8,
target_pointer_width = "32" => 4,
_ => 4,
};
Previously, a platform-specific constant value required either separate #[cfg(...)]-annotated declarations that duplicated the name and type, or a macro wrapper that amounted to the same indirection. cfg_select! handles this directly.
This follows a clear pattern in Rust’s evolution. The language proves ideas through popular crates, then absorbs them when they turn out to be universally necessary. lazy_static gave way to std::sync::LazyLock. once_cell’s OnceCell and Lazy types became OnceLock and LazyCell in the standard library. cfg-if is the next item in that sequence. The dependency disappears, the import disappears, and the syntax aligns with the rest of the language.
if-let Guards and What They Unlock in match
The second feature in 1.95 is if-let guards in match arms. To understand what changed, a quick recap of context: Rust 1.88 stabilized let chains, the feature that allows let bindings inside if conditions chained with &&:
// Let chains in if conditions, available since 1.88
if let Ok(body) = req.parse_body() && body.len() < 1024 {
process(body);
}
What 1.88 did not extend was match guards. A match guard is the optional if condition clause on a match arm:
match value {
Pattern(inner) if some_condition => { /* arm body */ }
_ => {}
}
Before 1.95, that condition had to be a plain boolean expression. You could call functions and combine results with &&, but you could not perform a let binding inside the guard. If you needed to attempt a conversion or fallible pattern match as part of deciding whether an arm applied, you had to push that check inside the arm body:
// Before 1.95: the structural split between match and inner check
fn handle(cmd: &str, payload: Option<&str>) {
match (cmd, payload) {
("execute", Some(s)) => {
if let Ok(id) = s.parse::<u64>() {
println!("Execute job #{}", id);
} else {
eprintln!("Invalid ID: {}", s);
}
}
("execute", None) => eprintln!("Missing payload"),
_ => {}
}
}
The if let Ok(id) check is logically a guard condition, but it lives inside the arm body. The fallthrough to the error case is an else clause rather than a separate match arm at the same level. As the logic grows more complex, this nesting compounds.
With 1.95’s if-let guards:
fn handle(cmd: &str, payload: Option<&str>) {
match (cmd, payload) {
("execute", Some(s)) if let Ok(id) = s.parse::<u64>() => {
println!("Execute job #{}", id);
}
("execute", Some(s)) => {
eprintln!("Invalid ID: {}", s);
}
("execute", None) => eprintln!("Missing payload"),
_ => {}
}
}
The binding introduced in the guard, id here, is available in the arm body. The two cases are separate arms at the same level. The structure of the code reflects the structure of the logic rather than working around a language limitation.
This combines with let chain semantics for more complex guards:
match event {
Event::Request(req)
if let Ok(body) = req.parse_body() && body.len() < 1024 => {
process_small_request(body);
}
Event::Request(_) => {
eprintln!("Request too large or malformed");
}
_ => {}
}
The && here uses let chain rules: body from the first binding is in scope for the size check on the right. This gives Rust’s match expressions the kind of guard expressiveness that pattern matching has in languages like OCaml and Haskell, where binding in guards has always been natural.
It is worth noting one constraint: variables bound in an if-let guard are only available when that guard matches. They are not in scope for subsequent arms or for other patterns in the same arm. This is consistent with how let chain bindings work in if conditions and avoids the scoping confusion that a less conservative design might introduce.
Practical Impact
Neither of these features changes what Rust programs can do. They change how clearly programs can express what they mean.
cfg_select! is part of the standard library now, so cross-platform crates that previously pulled in cfg-if can drop that dependency. For library authors maintaining crates with broad platform support, this shrinks the build graph and removes one item from the list of transitive dependencies that auditors and supply chain tools need to consider. The difference is small per crate; across the ecosystem it adds up.
If-let match guards will appear in any codebase that processes structured data through match expressions, which is most Rust code. The change makes match arms self-contained: the pattern, the guard condition including any sub-matching, and the body all live together without requiring inner if let nesting for what are logically guard checks.
Rust 1.95 is a steady-state release rather than a landmark one. The language’s fundamentals are not changing. What is changing, release by release, is the gap between what Rust can express and how naturally it can express it. These two features close two specific spots in that gap, both of which have been papered over long enough that removing the workarounds feels more significant than adding a new feature.