6.3. #[cfg] conditional compilation
The #[cfg(...)] attribute includes or excludes items from the build according to compile-time conditions. The grammar is:
cfg_predicate ::= identifier // flag
| identifier "=" string_lit // key=value
| "all" "(" pred_list ")"
| "any" "(" pred_list ")"
| "not" "(" cfg_predicate ")"
pred_list ::= cfg_predicate ("," cfg_predicate)* ","?
cfg_attr ::= "#" "[" "cfg" "(" cfg_predicate ")" "]"
cfg_attr_attr ::= "#" "[" "cfg_attr" "(" cfg_predicate ("," attr_body)+ ")" "]"
cfg_macro ::= "cfg" "!" "(" cfg_predicate ")"
6.3.1. Basic form
The simplest uses:
#[cfg(target_os = "linux")]
fn linux_specific() { ... }
#[cfg(target_arch = "x86_64")]
const HAS_AVX: bool = true;
#[cfg(test)]
mod tests { ... }
#[cfg(...)] takes a predicate as its argument. If the condition evaluates to true, the item is included in compilation; if false, it is excluded. An excluded item behaves as if it had never been written, so it is invisible to name resolution of other code as well.
6.3.2. Predicate kinds
key = value
A condition expressed by pairing a key with a value.
#[cfg(target_os = "linux")]
#[cfg(target_arch = "x86_64")]
#[cfg(target_pointer_width = "64")]
#[cfg(feature = "gpu")]
The standard keys are:
| Key | Possible values |
|---|---|
target_arch | "x86_64", "aarch64", "x86", "arm", "wasm32" |
target_os | "linux", "macos", "windows", "wasi", "none" |
target_pointer_width | "64", "32" |
target_endian | "little", "big" |
target_family | "unix", "windows", "wasm" |
target_env | "gnu", "musl", "msvc" |
feature | User-defined. Set by the CLI or the package manager |
target_family / target_env are absent when the target has none. Non-Tier-1 targets also give target_arch values "riscv64", "riscv32", "powerpc64", "s390x".
All target_* keys follow the build's --target (9.1.2). Without --target, the host target applies.
Plain flags
There is also a boolean flag form with only a key and no value.
#[cfg(test)] // only in test builds
#[cfg(debug_assertions)] // only in debug builds
Combinators
To combine several conditions, use combinators.
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[cfg(not(target_os = "windows"))]
The meaning of each combinator:
| Combinator | Meaning |
|---|---|
all(...) | Every argument is true |
any(...) | At least one is true |
not(...) | The argument is false |
Combinators may be nested freely.
#[cfg(all(
target_os = "linux",
any(target_arch = "x86_64", target_arch = "aarch64"),
not(feature = "minimal"),
))]
fn linux_native() { ... }
6.3.3. Attachment positions
#[cfg] applies to items, fields (named, tuple, union, variant and tuple-variant fields), enum variants, items of a trait, impl or extern block, statements, match arms, struct literal fields and function parameters. A cfg-false parameter is removed from the signature; calls pass the arguments of the remaining parameters. Closure parameters take no attributes.
#[cfg(...)] fn f() { ... }
#[cfg(...)] struct S { ... }
#[cfg(...)] mod m { ... }
#[cfg(...)] use foo::bar;
#[cfg(...)] static X: i32 = 0;
struct Foo {
#[cfg(target_pointer_width = "64")]
big_field: u64; // exists only on 64-bit
}
fn run(op: Op) {
#[cfg(test)] check(); // statement
match op {
Op::Add => add(),
#[cfg(debug_assertions)]
Op::Trace => trace(), // match arm
_ => {},
}
}
If the condition is false, the item is treated as nonexistent: it is removed right after parsing, before derives and other attributes see it (6.3.7). The output of meta-block expansions is parsed with the build's cfg, so #[cfg] inside it applies the same way.
6.3.4. #[cfg_attr(...)] conditional other attributes
#[cfg_attr] attaches another attribute conditionally.
#[cfg_attr(target_os = "windows", link("kernel32"))]
extern "C" { ... }
#[cfg_attr(test, derive(Debug))]
struct S { ... }
The form is cfg_attr(condition, attribute_to_apply); if the condition is true, the following attribute behaves as if attached directly. In the first example #[link("kernel32")] applies only in Windows builds; in the second, #[derive(Debug)] applies only in test builds.
Several attributes can be applied at once.
#[cfg_attr(test, derive(Debug), allow(unused))]
struct S { ... }
6.3.5. cfg!(...) compile-time bool value
cfg!(...) appears in expression position and evaluates to a bool at compile time.
const IS_LINUX: bool = cfg!(target_os = "linux");
if cfg!(feature = "gpu") {
use_gpu()
} else {
use_cpu()
}
It takes the same predicate as #[cfg], and the result is a const expression in the sense of const, so it can be used in other const positions as well. Note that cfg!, like include_str! / include_bytes! (6.11), is a compiler built-in form, not a macro (the call-triggered meta definition, 6.8.12): it is fixed in the compiler and cannot be redefined or shadowed, and its name is reserved.
The difference between #[cfg] and cfg!():
#[cfg]: includes or excludes an entire item from the build.cfg!(): looks like a runtime branch but is actually a compile-time constant, so one branch is removed by dead-code elimination.
// #[cfg] - only one of the two functions is compiled
#[cfg(feature = "gpu")] fn run() { use_gpu() }
#[cfg(not(feature = "gpu"))] fn run() { use_cpu() }
// cfg!() - branch within one function; the compiler removes one side
fn run() {
if cfg!(feature = "gpu") { use_gpu() } else { use_cpu() }
}
Choose between the two forms according to convenience and code structure.
6.3.6. Where parameters come from
target_* keys
The target_* keys are set automatically according to the build's target platform. They have well-defined values for the 15 Tier 1 targets in platform targets.
The feature key
The feature key is set by a CLI option or by the package manager and build tools.
rokc --cfg feature=gpu
The package manager rok sets it per package: each package is compiled with feature = "<f>" for exactly the features the build enables for it (9.10.4c). rokc --cfg feature=<f> sets a feature of the package being compiled only; a dependency's features are given with --extern-feature <name>=<f> (9.1), and the standard library has none.
test / debug_assertions
These two flags are determined by the build mode.
test: true when building withrokc testor an equivalent command.debug_assertions: true at-O0.
6.3.7. When it is evaluated
#[cfg] is evaluated right after parsing. Items with false conditions are removed from the AST immediately, and later passes (name resolution, type checking, code generation) see only the remaining AST.
This precedes the meta-expansion pass (template tokens and expansion), so user-defined derives and attributes attached to items whose cfg is false are not processed either.
6.3.8. Negated / conflicting cfgs
If conditions are true simultaneously in the same build and two definitions survive, the ordinary duplicate definition error occurs.
#[cfg(target_os = "linux")]
fn impl_a() { ... }
#[cfg(target_os = "linux")] // same cfg + same name → conflict
fn impl_a() { ... } // ERROR - both definitions exist in the same build
The following is fine, because the two cfgs are mutually exclusive and only one definition is active in any build.
#[cfg(target_os = "linux")]
fn impl_a() { ... }
#[cfg(not(target_os = "linux"))]
fn impl_a() { ... } // OK - the two cfgs are mutually exclusive
6.3.9. cfg and modules/files
A cfg can be attached to a whole module.
#[cfg(target_os = "linux")]
mod linux_only {
// this whole module exists only in Linux builds
}
The module's files and directories still exist on disk, but if the cfg is false they are not read, or are discarded immediately if read.
6.3.10. Macros inside cfg (none)
Arbitrary expressions or macro calls cannot appear inside a cfg predicate. Only the fixed key-value forms and combinators are allowed.
#[cfg(custom_macro!())] // not adopted
6.3.11. Debugging tools
A command such as rokc --print cfg prints the cfg values active in the current build (the exact command follows the compiler specification).