· 5 min read ·

Rust 1.95 Absorbs What the Ecosystem Built Around the Language

Source: rust

The Rust 1.95.0 release is not the kind of release that generates conference talks. There is no new async runtime primitive, no major borrow checker overhaul, no headline memory model change. What it does instead is something arguably more valuable: it folds two long-standing ecosystem workarounds directly into the language.

Both of the headlining features, cfg_select! and if-let guards in match expressions, represent gaps between what Rust developers needed and what the language natively provided. The community filled those gaps, sometimes with widely-used crates, sometimes with verbose pattern-matching boilerplate. 1.95 closes them properly.

cfg_select! and the Shadow It Casts on cfg-if

Conditional compilation in Rust has always been syntactically awkward. The #[cfg(...)] attribute works well for single items, but when you need to conditionally define multiple items or choose between several platform implementations, you have historically had two options: repeat the same #[cfg] attribute on every item, or reach for the cfg-if crate.

cfg-if was written by Alex Crichton and became one of the most downloaded crates in the Rust ecosystem, routinely appearing in the top ten by dependency count. Its popularity was not because it was elegant. It was popular because the alternative was worse. The syntax looked like this:

cfg_if::cfg_if! {
    if #[cfg(unix)] {
        fn foo() { /* unix specific functionality */ }
    } else if #[cfg(target_pointer_width = "32")] {
        fn foo() { /* non-unix, 32-bit functionality */ }
    } else {
        fn foo() { /* fallback implementation */ }
    }
}

This works, and millions of crates depend on it, but the syntax is a strange hybrid: it looks like an if-else chain, wrapped in a macro, with #[cfg(...)] attributes floating inside expression position where attributes do not normally appear. It reads like a workaround because it is a workaround.

cfg_select! in 1.95 provides the same capability with syntax that fits naturally into how Rust already thinks about multi-way conditionals:

cfg_select! {
    unix => {
        fn foo() { /* unix specific functionality */ }
    }
    target_pointer_width = "32" => {
        fn foo() { /* non-unix, 32-bit functionality */ }
    }
    _ => {
        fn foo() { /* fallback implementation */ }
    }
}

The resemblance to match is deliberate. The macro expands to the right-hand side of the first arm whose configuration predicate evaluates to true. If no arm matches and there is no wildcard, it is a compile error. The wildcard _ arm works exactly as you would expect from a match expression.

It also supports expression-position use, not just item-level definitions:

let is_windows_str = cfg_select! {
    windows => "windows",
    _ => "not windows",
};

This is cleaner than either repeating cfg annotations or using the cfg!() macro in a chain of if-else expressions. The cfg-if crate had no clean equivalent for this inline use case.

For the broader ecosystem, this creates an interesting migration question. Crates that currently depend on cfg-if are not broken; the crate is not going anywhere. But new code now has a built-in option that requires no dependency and carries more idiomatic syntax. Over time, the same pattern will play out that happened with once_cell when std::sync::OnceLock arrived, or with lazy_static when LazyLock was stabilized. The ecosystem crate served its purpose; the language learned from it.

if-let Guards: Let Chains Come to match

To understand if-let guards in match, you need to understand where they came from. Rust 1.64 added let-else, which lets you write let Ok(val) = result else { return; }. Rust 1.88 stabilized let chains, which let you chain multiple let bindings and boolean conditions inside if and while expressions using &&:

if let Some(user) = get_user(id) && user.is_active() && let Some(session) = get_session(&user) {
    // user, session both bound here
}

This was a significant ergonomic improvement for code that needed to conditionally unwrap multiple values. But match expressions were left behind. Match guards, the if condition clause at the end of a match arm, remained limited to boolean expressions. If you needed to do something like unwrap a value inside a match guard, you had to either restructure the match entirely or introduce a temporary variable outside the match.

Rust 1.95 addresses this by allowing if let patterns inside match guards:

match message {
    Event::Response(data) if let Ok(parsed) = serde_json::from_str::<Response>(&data) => {
        handle_response(parsed);
    }
    Event::Response(_) => {
        eprintln!("failed to parse response");
    }
    _ => {}
}

The binding introduced by the if-let guard (parsed in the example above) is available in the match arm’s body. Without this feature, the equivalent code required either a nested if let inside the arm body (which meant an additional indentation level and a loss of exhaustiveness checking at the outer pattern level) or moving the parse call outside the match.

This also composes with the rest of let chains. You can chain multiple conditions in a match guard:

match event {
    Message(text) if let Ok(cmd) = parse_command(&text) && cmd.is_authorized() => {
        execute(cmd);
    }
    _ => {}
}

The combination of pattern matching on the outer value and conditional refinement in the guard is something that languages like Scala and Haskell have long supported through various guard syntaxes. Rust’s version is grounded in its ownership model: the bindings from the if-let guard follow normal borrow checker rules, and if the guard evaluates to false, no binding is created and no ownership is transferred.

The Pattern Behind the Features

Looking at these two features together, they share a common origin: Rust programmers identified patterns they needed, the language did not support them natively, and workarounds emerged. For conditional compilation, the workaround became a crate depended on by half the ecosystem. For match guards, the workaround was structural, reshaping match expressions into less natural forms.

This is not a criticism of how Rust was designed. It reflects something that most successful languages go through. The language launches with a coherent core, the community builds on top of it, and over time the most proven patterns get absorbed back into the language proper. The difference with Rust is that the feedback loop between crate ecosystem and language design is unusually direct, with tooling like crates.io dependency counts providing a quantitative signal about what people actually need.

For those writing cross-platform Rust today, cfg_select! is worth adopting immediately in new code. The syntax is more readable than cfg-if and carries no dependency cost. For those doing complex event processing or protocol parsing where match expressions over structured data are common, if-let guards will let you flatten some genuinely awkward nesting.

Neither feature will change how you think about the language. Both will make the code you already write slightly cleaner, which, compounded across a codebase, turns out to matter quite a bit.

Was this interesting?