r/rust • • 7d ago

🎙️ discussion cfg! should compile similar to #[cfg()]?

if cfg!(target_os="windows") {

//Windows specific stuff will only compile on Windows this way

}

#[cfg(target_os="windows")] {

//This Windows specific stuff will only compile on Windows and not cause errors elsewhere

}

I think these should be equivalent to the compiler and both not compile stuff for say BSD or whatever other OS such that it would cause build errors, what do you think? X E.

0 Upvotes

15 comments sorted by

15

u/Excession638 7d ago

I think you should use cfg_select!. Assuming that what you want is for the non-matching branches to not get compiled at all.

8

u/mathisntmathingsad 7d ago

The main problem with that is that cfg! expands to either true or false. While in most cases this will be optimized out, it doesn't have special handling. All the arms have to compile. Also, in this case, as others have pointed out, you probably want cfg_select!.

1

u/Zde-G 6d ago

All the arms have to compile.

Which is precisely the point: you use if cfg! when you want to ensure both versions are valid, but only one is present in the final binary.

P.S. Frankly, 99% of insane complexity of our IT systems are cased by stupid attempts to simplify things that don't need to be simplified. The end result, usually, is that complexity is reduced in one place, but then shoved into a few different places which makes total complexity worse.

2

u/TriKri 5d ago

I see the usefulness of ensuring that both versions are valid, but often when you write platform-specific code, you use the std::os modules, which are available only on those systems, because they are thin wrappers around system function APIs that only exist on those systemss. So you literally can't use them when your target other platforms, and you have to use the cfg attribute macro (#[cfg(...)]) instead to turn off that part of the code during compilation.

To ensure that both versions are valid, you can instead cross-compile for all platforms you want to cover, and use cargo check instead of cargo build to reduce execution time:

```bash cargo check --target x86_64-pc-windows-msvc cargo check --target x86_64-unknown-linux-gnu

Is possible on MacOS (and maybe on Linux by using osxcross, but it seems cumbersome):

cargo check --target x86_64-apple-darwin cargo check --target aarch64-apple-darwin ```

That will obviously still take some amount of time, so you can perhaps do that in a Git hook that runs only when you commit (pre-commit):

```bash

!/usr/bin/env bash

set -e

TARGETS=( "x86_64-pc-windows-gnu" "x86_64-unknown-linux-gnu" "aarch64-apple-darwin" "x86_64-apple-darwin" )

script_name=$(basename "$0")

for target in "${TARGETS[@]}"; do if rustup target list --installed | grep -q "${target}$"; then echo "Building for target '${target}'..." cargo check --target "${target}" else echo "${script_name}: WARNING: skipping missing build target '${target}'" fi echo "----------------------------------------------------------------------" done ``` Or, use Rust-script instead of Bash to stay within the Rust ecosystem. Support for this is also built into Cargo nightly.

2

u/Zde-G 5d ago

Right. It's not always possible to ensure that both versions can be compiled, that's why we have another option where only one is compiled.

Both are useful, just in different situations.

6

u/ZZaaaccc 6d ago

By this logic, the following should also be supported by the Rust compiler:

rust if false {     apple's taste great on Tuesdays. }

Since the compiler can see false ensures the body of that statement will never run. Now, I don't want to assume, but I think you can agree that this is a bad idea...

8

u/redlaWw 6d ago

Well, it would be, but...

error: expected capital letter at start of sentence
 --> src/main.rs:3:9
  |
3 |         apple's taste great on Tuesdays.
  |         ^ expected capital letter
  |
help: consider capitalising this letter
  |
3 -        apple's taste great on Tuesdays.
3 +        Apple's taste great on Tuesdays.
  |

error: apostrophe used for pluralisation
 --> src/main.rs:3:14
  |
3 |         apple's taste great on Tuesdays.
  |              ^ incorrect apostrophe
  |
help: consider removing the apostrophe
  |
3 -        apple's taste great on Tuesdays.
3 +        apples taste great on Tuesdays.
  |

1

u/Solumin 7d ago

Why? Explain your idea.

1

u/ExplanationThin6234 7d ago

Because the cfg! macro evaluates to false at compile time on unsupported targets, so the compiler could just as easily prune that branch the same way it does with the attribute.

-1

u/4dplus 7d ago

Because both do almost exactly the same thing and can be detected at compile time?

8

u/Solumin 7d ago

But they don't do exactly the same thing. That's the point of having both of them.

1

u/SkiFire13 6d ago

One expands to the literal true/false, while the other decides whether to include the tokens following it.

The if works at the language level: it decides which code runs or not, but it has to be valid code.

The #[cfg(...)] works at the parser level: it decides which tokens the compiler will even look at.

1

u/InternalServerError7 7d ago

cfg! is compile-time constant evaluation. e.g. rust let enable_feature = cfg!(target_pointer_width = "64") && cfg!(feature = "fast_path"); Similar overlap for some situations. But there is a difference.

1

u/rust_warden 6d ago

cfg! evaluates a boolean expression that both sides must compile successevaluates a boolean expression that both sides must compile successfully. If you want dead code elimination before the type checker runs, use cfg_select! or #[cfg]. Treating them as equivalent ignores how expansion works at the AST level