cfg!
The cfg!(predicate) macro is a built-in standard library macro that evaluates a conditional compilation predicate at compile time and expands to the boolean literal true or false. Unlike the #[cfg(...)] attribute, which strips unpicked items from the Abstract Syntax Tree, cfg!(...) is an ordinary boolean expression where every branch is still parsed and type-checked on all targets.
Critical Difference Between #[cfg(...)] and cfg!(...)
`#[cfg(windows)]` Attribute:
#[cfg(windows)]
{ win32_only_api(); } // Stripped from AST on Unix -> Does NOT need to exist on Unix!
`cfg!(windows)` Macro:
if cfg!(windows) {
win32_only_api(); // Expands to `if false { win32_only_api(); }` on Unix
} // -> MUST still parse & type-check on Unix! (Dead-code eliminated later)
| Property | #[cfg(pred)] Attribute |
cfg!(pred) Macro |
|---|---|---|
| Syntactic role | Attribute attached to an item, block, or statement | Boolean expression (bool: true or false) |
| Inactive branch type-checked? | No (removed from AST before type checking) | Yes (must be valid, type-correct Rust on all platforms!) |
Can use platform-only symbols (std::os::unix) in inactive branch? |
Yes | No (fails to resolve on Windows!) |
Can be stored in a const / let variable? |
No | Yes (const IS_DEBUG: bool = cfg!(debug_assertions);) |
| Runtime branch cost | 0 |
0 (constant-folded and dead-code eliminated by compiler) |
Using cfg!(...) for Portable Configuration Selection
When both branches of an if/else expression use portable, cross-platform types (such as selecting buffer sizes, default file paths, or logging verbosity), cfg!(...) is cleaner than duplicating entire functions with #[cfg(...)] and #[cfg(not(...))] because the compiler guarantees that both branches remain syntactically and type-valid on every target:
const DEFAULT_BUFFER_SIZE: usize = if cfg!(debug_assertions) {
1024
} else {
64 * 1024
};
fn platform_line_ending() -> &'static str {
if cfg!(windows) { "\r\n" } else { "\n" }
}
fn describe_environment() {
let os_family = if cfg!(unix) {
"unix-like"
} else if cfg!(windows) {
"windows"
} else {
"other"
};
let pointer_bits = if cfg!(target_pointer_width = "64") {
64
} else {
32
};
println!(
"family = {}, bits = {}, default_buf = {} B, newline_len = {}",
os_family,
pointer_bits,
DEFAULT_BUFFER_SIZE,
platform_line_ending().len()
);
}
fn main() {
describe_environment();
}
family = unix-like, bits = 64, default_buf = 1024 B, newline_len = 1
Why Platform-Specific APIs Require #[cfg] Instead of if cfg!
If you call a Unix-only function (like std::os::unix::fs::MetadataExt::ino) inside if cfg!(unix) { ... }, compilation will fail on Windows because std::os::unix does not exist on Windows targets and if false { ... } is still type-checked. Always use #[cfg(unix)] whenever the code inside references symbols or types that only exist when the predicate holds.