cfg_select! and if-let Match Guards: Rust 1.95 Closes Two Long-Standing Gaps
Source: rust
The cfg-if Problem
Conditional compilation in Rust has always required a certain amount of awkwardness. The #[cfg(...)] attribute works well for individual items, and cfg!(...) works for simple boolean checks in expressions, but neither handles the common pattern of “compile this function one way on Unix, another way on Windows, and a third way everywhere else” without repetition or indirection.
For years, the community answer was the cfg-if crate, written by Alex Crichton and now one of the most downloaded Rust crates in existence. Its usage looks like this:
cfg_if::cfg_if! {
if #[cfg(unix)] {
fn platform_path() -> &'static str { "/usr/local" }
} else if #[cfg(windows)] {
fn platform_path() -> &'static str { "C:\\Program Files" }
} else {
fn platform_path() -> &'static str { "/opt" }
}
}
It works. It has been working for nearly a decade. But the if #[cfg(...)] syntax is clearly a workaround, not a designed feature. You are nesting a cfg attribute inside a macro to get branching behavior that the language itself did not provide.
cfg_select! in 1.95
Rust 1.95.0 stabilizes cfg_select!, a built-in macro that handles the same job with a syntax that reads like what it is. The arms look like match arms, where each pattern is a cfg predicate:
cfg_select! {
unix => {
fn platform_path() -> &'static str { "/usr/local" }
}
windows => {
fn platform_path() -> &'static str { "C:\\Program Files" }
}
_ => {
fn platform_path() -> &'static str { "/opt" }
}
}
The macro expands to the right-hand side of the first arm whose predicate evaluates to true. The _ arm is the catch-all fallback. If no arm matches and there is no _, you get a compile error, which is the correct behavior.
The readability improvement is immediate. The predicates read like configuration conditions, not like attributes smuggled inside a macro invocation. The match-arm structure is already familiar to every Rust programmer, and the mental model transfers directly.
More importantly, cfg_select! works in expression position by design:
let sep = cfg_select! {
windows => "\\",
_ => "/",
};
let is_unix = cfg_select! {
unix => true,
_ => false,
};
You can use it directly in a let binding, in a function argument, or anywhere an expression is expected. Non-selected arms are never compiled, so invalid code in excluded branches does not cause errors. This matches the behavior of #[cfg(...)] attributes on items: only the selected branch exists from the compiler’s perspective.
Cfg predicates support the full range of combinators you already know: any(...), all(...), not(...), and compound conditions like target_pointer_width = "32". These work in cfg_select! arms without any special handling:
cfg_select! {
all(unix, not(target_os = "macos")) => {
fn setup_inotify() { /* Linux-specific */ }
}
target_os = "macos" => {
fn setup_inotify() { /* kqueue-based on macOS */ }
}
_ => {
fn setup_inotify() { /* no-op on other platforms */ }
}
}
cfg_select! is not replacing cfg!(...) for single-condition boolean checks, nor #[cfg(...)] attributes on individual items. Those remain the right tools for their respective use cases. The new macro targets the multi-branch conditional compilation pattern specifically, which is exactly where cfg-if earned its ubiquity.
For library authors, one practical benefit is the removal of cfg-if as a build dependency. In large compilation graphs, even trivial dependency reductions reduce build times and supply chain surface area. Cfg-if was never a heavy dependency, but replacing it with a standard macro is a clean improvement for new projects.
if-let Guards in Match Expressions
The second major feature in 1.95 builds directly on let chains, which were stabilized in Rust 1.88. Let chains allow multiple let bindings and boolean conditions to be combined in if and while expressions using &&:
if let Some(user) = get_user()
&& user.is_active()
&& let Some(token) = user.auth_token()
{
authenticate(user, token);
}
What let chains did not cover was match guards. A match guard is the if condition appended to a match arm:
match event {
Event::Login(user_id) if is_allowed(user_id) => { /* handle login */ }
_ => {}
}
Prior to 1.95, guards were restricted to boolean expressions. You could not bind new variables through a let pattern inside a guard. If you needed to both check a condition and bind part of its result for use in the arm body, the usual approach was to match first and then run a nested if let inside the arm:
match request {
Request::Upload(data) => {
if let Ok(parsed) = parse_data(&data) {
process(parsed);
} else {
return Err("invalid data");
}
}
_ => {}
}
With if-let guards in 1.95, the condition and the binding can live where they logically belong, in the guard itself:
match request {
Request::Upload(data) if let Ok(parsed) = parse_data(&data) => {
process(parsed);
}
_ => {
return Err("invalid request");
}
}
The variable parsed is bound by the guard and available throughout the arm body. The arm only executes when both the pattern matches and the guard succeeds, which is consistent with how boolean guards have always worked.
Because let chains carry over, you can compose multiple bindings and conditions in a single guard:
match message {
Message::Update { id, payload }
if let Some(record) = store.get(id)
&& record.version < payload.version => {
store.update(id, payload);
}
_ => {}
}
This reads top to bottom without indentation forcing the logic sideways. The pattern handles the structural match, the guard handles auxiliary state and conditions, and the body only runs when everything is satisfied.
One thing worth being explicit about: bindings from if-let guards are scoped to their own arm, not shared across arms, and guard evaluation happens at runtime. This is in contrast to cfg_select!, where everything is resolved at compile time. The two features address different layers of Rust’s conditional logic, but both work toward the same goal of letting you express intent directly rather than through indirection.
What This Release Reflects
Both features in 1.95 follow a pattern Rust has used throughout its development: identify recurring workarounds that the community has converged on, understand why the workaround exists, and provide a first-class alternative that handles the common case cleanly.
Cfg-if became ubiquitous because the language lacked built-in multi-branch conditional compilation. If-let guards in match were worked around with nested conditionals because the guard syntax was underexpressive relative to the if condition syntax after let chains shipped. Neither workaround was terrible, but both imposed cognitive overhead on code that should have been straightforward to write.
The cfg-if crate will keep working. Existing code using it does not need to change. Over time, cfg_select! will become the natural starting point for new multi-branch cfg logic, particularly in code that needs expression-position conditional compilation.
For the full list of stabilized library APIs and other changes in this release, the Rust 1.95.0 release notes cover everything in detail. If you are on a previous version, rustup update stable gets you there.