· 5 min read ·

cfg_select! and the Art of Graduating Crates Into the Language

Source: rust

Rust 1.95.0 landed on April 16, 2026, and two of its headline features share something in common: they both complete ideas that have been partially solved in the ecosystem for years. The cfg_select! macro graduates a widely-used crate pattern into the language itself. The stabilization of if-let guards in match arms extends the let-chain capability from Rust 1.88 into a context where it was conspicuously missing. Neither feature is revolutionary on its own, but together they close real gaps in expressiveness that Rust developers have been working around for a long time.

cfg_select! and the cfg-if Problem

Conditional compilation in Rust has always worked through #[cfg(...)] attributes and the cfg!() macro. These tools handle most cases well: you can gate an item on unix, require a specific target_arch, or check for a feature flag. Where things get awkward is when you need to express mutually exclusive alternatives, the compile-time equivalent of an if-else chain.

The cfg-if crate has been the standard answer to this for years. Created by Alex Crichton and now maintained by the Rust community, it provides a macro that lets you write branching cfg logic without repeating yourself. It looks like this:

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 cfg-if appears in the dependency tree of an enormous number of crates. But the syntax is unusual. It looks superficially like Rust code but uses a different structure that a reader needs to parse mentally as a separate mini-language. The if #[cfg(...)] form inverts the normal attribute position, and it only works at item level, not in expression context.

Rust 1.95 introduces cfg_select!, which solves the same problem with match-like syntax:

cfg_select! {
    unix => {
        fn platform_name() -> &'static str { "unix" }
    }
    windows => {
        fn platform_name() -> &'static str { "windows" }
    }
    _ => {
        fn platform_name() -> &'static str { "other" }
    }
}

The structure maps directly onto Rust’s existing match arm syntax. Cfg predicates go on the left, the expansion goes on the right, and _ serves as the required fallback arm. Crucially, cfg_select! also works in expression position:

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

This is something cfg-if cannot do cleanly. With the older approach you would either reach for the cfg!() macro (which returns a bool and requires you to write both branches anyway) or restructure the code so that the conditional is at item level. With cfg_select! in expression position, platform-specific string constants, numeric values, or small expressions are straightforward to express inline.

What Makes cfg_select! Different from cfg!()

The distinction between cfg!() and cfg_select! is worth being precise about. cfg!() evaluates a single predicate and returns true or false. You can use it in a boolean expression, but it does not let you select between two different values or types; both branches of any if cfg!(unix) { A } else { B } expression must type-check simultaneously.

cfg_select!, by contrast, expands to only the matching arm. The compiler never sees the non-selected branches as code to type-check. This means you can have arms that use platform-specific types or functions that do not exist on other targets, which is exactly the use case that drove the adoption of cfg-if in the first place. Low-level crates that wrap OS-specific APIs, cross-platform abstractions, and anything touching FFI all benefit from this property.

The macro evaluates predicates in order and expands to the first matching arm, the same short-circuit semantics as a match expression. This ordering guarantee matters when predicates overlap: the most specific condition should come first, with the wildcard arm at the end.

if-let Guards in Match Expressions

The second major feature in 1.95 extends the let chains introduced in Rust 1.88. Let chains allowed chaining multiple let bindings in if and while conditions:

// Available since 1.88
if let Some(user) = find_user(id) && let Ok(profile) = load_profile(&user) {
    display(profile);
}

The 1.88 stabilization deliberately excluded match arm guards. Match arms already support guard expressions via if condition, but before 1.95, that condition had to be a plain boolean expression. You could not bind a new variable in the guard and use it in the arm body.

With 1.95, if-let guards are stable:

match event {
    Event::Message(raw) if let Ok(parsed) = parse_message(&raw) => {
        handle(parsed);
    }
    Event::Message(_) => {
        log::warn!("unparseable message");
    }
    _ => {}
}

The parsed binding is available in the arm body when the guard matches. Without this feature, the equivalent code required either nesting a second match or if let inside the arm body (after matching on the outer pattern), or restructuring so the parsing happened before the match. Both workarounds fragment the logic and make the control flow harder to follow at a glance.

This pattern comes up frequently when working with event-driven code, particularly in async contexts where you receive untyped or loosely-typed messages and need to pattern-match while simultaneously attempting a fallible conversion. Discord bot code is full of this shape: you match on the event variant, then attempt to extract or parse the payload, and handle the failure case separately. Having the guard and the binding in one place, rather than split across the pattern and the body, is a meaningful improvement.

The Ecosystem Graduation Pattern

Zooming out, cfg_select! follows a pattern that Rust has used deliberately over time. The ecosystem discovers a pattern, a crate emerges to address it, that crate becomes near-universal, and eventually the capability earns a place in the language or standard library. cfg-if is one of the cleaner examples: the problem was real, the crate solution was pragmatic but syntactically odd, and the language eventually provides something better.

This approach has costs and benefits. The cost is that the ecosystem runs on cfg-if for years and cfg_select! must coexist with it during a long transition. Existing crates that remove their cfg-if dependency will need to bump their minimum supported Rust version, which not all maintainers can do quickly. The benefit is that the language gets to observe real usage before committing to a design, rather than specifying features in advance of actual need.

Other languages handle this differently. Zig bakes platform-specific branching into the comptime system directly, eliminating the need for a separate mechanism. C relies on the preprocessor, which is powerful but operates entirely outside the type system. Rust’s approach of starting with attributes and macros, then promoting common patterns to first-class syntax, sits between these poles: more principled than the preprocessor, more incremental than designing everything upfront.

Upgrading

If you have Rust installed via rustup, the upgrade is a single command:

$ rustup update stable

The full release notes for 1.95.0 cover the complete set of library stabilizations and compiler changes beyond what the announcement highlights. As with any Rust release, the stabilized surface area is a subset of what has been in nightly; if you have been tracking nightly for cfg_select! or if-let guards specifically, the stable release means you can drop the feature gates and declare a stable MSRV.

For projects that care about MSRV compatibility, 1.95 is now the minimum to use either feature without feature flags. The broader ecosystem will take time to follow, but for internal projects or applications where you control the toolchain, there is no reason to wait.

Was this interesting?