# \#\[cfg\] if-then-else

**URL:** https://internals.rust-lang.org/t/cfg-if-then-else/22052
**Category:** language design
**Created:** [December 20, 2024, 7:57pm UTC](https://internals.rust-lang.org/t/cfg-if-then-else/22052 "2024-12-20T19:57:20Z")
**Posts on this page:** 8
**Page:** 1

<div class="post-metadata">

### Author: ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)
#### Post date: [December 20, 2024, 7:57pm UTC](https://internals.rust-lang.org/t/cfg-if-then-else/22052/1 "2024-12-20T19:57:20Z")

</div>

I had an idea in the middle of the "[Testing and mocking based on name conversion](https://internals.rust-lang.org/t/testing-and-mocking-based-on-name-conversion/22034)" thread that I think should maybe be discussed independently.

If you want to conditionally compile one item under some `cfg` condition, and a different item under the opposite of that `cfg` condition, right now you're forced to repeat the condition:

```rust
#[cfg_attr(test, path = "database_mock.rs")]
#[cfg_attr(not(test), path = "database_real.rs")]
mod database;

```

... and that's probably the _nicest_ case. It can get much worse:

```rust
#[cfg(target_os = "macos")]
pub(crate) fn get_screen_resolution() {
   // several dozen lines of code
}
#[cfg(target_os = "linux")]
pub(crate) fn get_screen_resolution() {
   // several dozen lines of code
}
#[cfg(target_os = "windows")]
pub(crate) fn get_screen_resolution() {
   // several dozen lines of code
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
pub(crate) fn get_screen_resolution() {
   unimplemented!();
}

```

I think this points at a genuine missing language feature: compile-time cfg-based if-then-else chains.

`cfg_attr` can easily be extended to support if-then-else, we just need some notation, perhaps

```rust
#[cfg_attr(
   test => path = "database_mock.rs";
   target_os = "android" => path = "database_android.rs";
   /* otherwise */ path = "database_default.rs";
)]
mod database;

```

(semicolons separate the arms so you can still write multiple comma-separated attributes in each arm)

I don't, however, have a suggestion I 100% like for plain `cfg` applied to an item. The [cfg-if](https://docs.rs/cfg-if/latest/cfg_if/) crate lets you write, effectively, `if cfg!(...) { ... } else if cfg!(...) { ... } else { ... }` at file scope, and _abstractly_ I think that's a nice notation, but I'm worried that people would immediately want to generalize it to arbitrary (const) controlling expressions and beyond, and maybe we don't want to let that genie out of its bottle?

---

<div class="post-metadata">

### Author: ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)
#### Post date: [December 20, 2024, 9:00pm UTC](https://internals.rust-lang.org/t/cfg-if-then-else/22052/2 "2024-12-20T21:00:36Z")

</div>

`cfg-if` does seem to be very popular for this purpose.

We're talking about adding something like this in the standard library: [cfg\_match in std - Rust](https://doc.rust-lang.org/std/macro.cfg_match.html)

---

<div class="post-metadata">

### Author: ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)
#### Post date: [December 20, 2024, 9:14pm UTC](https://internals.rust-lang.org/t/cfg-if-then-else/22052/3 "2024-12-20T21:14:17Z")

</div>

The proposed `cfg_match` notation looks pretty solid to me. The strongest argument I can think of for built-in `if cfg!(...)` at top level instead is that it would get rid of a nesting level -- compare

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

```

vs

```rust
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 maybe isn't the ideal example to show the tradeoffs involved, especially considering _this_ is DRYer and also stable:

```rust
fn foo() {
    if cfg!(unix) {
        /* unix specific functionality */
    } else if cfg!(target_pointer_width = "32") {
        /* non-unix, 32-bit functionality */
    } else {
        /* fallback implementation */
    }
}

```

I'd expect existing users of `cfg-if` at top level are using it to conditionalize _groups_ of items, and for that ... maybe the extra nesting level is _more_ of a problem than it would be for variations of a single function? I am not actually familiar with any examples.

---

<div class="post-metadata">

### Author: ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)
#### Post date: [December 20, 2024, 9:30pm UTC](https://internals.rust-lang.org/t/cfg-if-then-else/22052/4 "2024-12-20T21:30:58Z")

</div>

BTW, note that there's currently a proposal to simplify the syntax to drop the repeated `cfg()`:

```rust
cfg_match! {
    unix => {
        /* unix specific functionality */
    }
    target_pointer_width = "32" => {
        /* non-unix, 32-bit functionality */
    }
    _ => {
        /* fallback implementation */
    }
}

```

---

<div class="post-metadata">

### Author: ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)
#### Post date: [December 20, 2024, 10:56pm UTC](https://internals.rust-lang.org/t/cfg-if-then-else/22052/5 "2024-12-20T22:56:44Z")

</div>

> [@zackw](#):
>
> I'm worried that people would immediately want to generalize it to arbitrary (const) controlling expressions and beyond

This is unfortunately a somewhat interesting question to ask. How/why is `cfg!` made special here; is any _macro_ expanding to a literal `true`/`false` okay?

Separately, how do we explain the different behavior of `if cfg!` at item scope and statement scope? Specifically, at statement scope `if` introduces a new scope (outside can't use names defined inside) and is checked at runtime (both branches must compile even if the controlling expression is a literal), but at item scope it chooses one arm to be compiled at the item scope and and discards the other arms. For this reason I prefer `static if`\[1\] or some other way to differentiate from standard `if` and allows it to be used in expression position, but that also makes the door to arbitrary controlling expressions much easier to open unless the syntax directly bakes in that it's a `cfg` expression.

So imo a std `cfg_match!` is the way to go for `cfg`s.

* * *

1. People often call this `const if`, but I don't like that, as `if const` is valid and means something very different. A horrible but funny idea: `if!`

---

<div class="post-metadata">

### Author: ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)
#### Post date: [December 21, 2024, 3:48am UTC](https://internals.rust-lang.org/t/cfg-if-then-else/22052/6 "2024-12-21T03:48:01Z")

</div>

> [@CAD97](#):
>
> How/why is `cfg!` made special here; is any _macro_ expanding to a literal `true`/`false` okay?

Having thought about it some more, I think the hypothetical module-scope `if` _should_ accept an arbitrary `const` controlling expression. This is not harder to implement or explain than module-scope `const` data items. In fact, having module-scope `if` accept a _different_ set of expressions from those that work as `const` data item initializers would be _harder_ to explain.

> [@CAD97](#):
>
> how do we explain ...[that] at statement scope `if` introduces a new scope (outside can't use names defined inside) and is checked at runtime (both branches must compile even if the controlling expression is a literal), but at item scope it chooses one arm to be compiled at the item scope and and discards the other arms

You just did explain it! 😉

Seriously, I don't see this as a problem. It does the most natural thing for it to do in each context; it's fine that the most natural thing happens to be different at module scope than statement scope. People learning the language will hear the explanation, go "oh, of course", and carry on.

---

<div class="post-metadata">

### Author: ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)
#### Post date: [December 21, 2024, 7:13am UTC](https://internals.rust-lang.org/t/cfg-if-then-else/22052/7 "2024-12-21T07:13:15Z")

</div>

> [@zackw](#):
>
> Seriously, I don't see this as a problem. It does the most natural thing for it to do in each context; it's fine that the most natural thing happens to be different at module scope than statement scope.

Except that you can write items at statement scope. So what are people who want the cfg-discard behavior at statement scope supposed to do? Or as you said, it's possible to do `if cfg!` around a `fn` or inside the `fn`, and if the function signature is involved, it can be desirable to not duplicate it, but in that case the discarded `if` arms aren't allowed to use any names which are cfg gated.

For the same reason Rust uses `if` identically both for statement and expression position instead of having a separate ternary expression, imo it's just simpler to have a separate `static if` (or whatever syntax) for item position, rather than reuse the exact same `if` syntax.

---

<div class="post-metadata">

### Author: ![steffahn](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/steffahn/32/13288_2.png) [@steffahn](https://internals.rust-lang.org/u/steffahn)
#### Post date: [June 14, 2026, 7:13am UTC](https://internals.rust-lang.org/t/cfg-if-then-else/22052/8 "2026-06-14T07:13:43Z")

</div>

This topic was automatically closed 540 days after the last reply. New replies are no longer allowed.
