Rust cfg_attr Patterns
cfg_attr is an attribute that applies another attribute only when a condition holds. One use lends a struct Debug printing only in debug builds.
An attribute behind a feature
Card earns Debug printing only when the fancy feature is on. In a plain build, the derive simply never happens:
#[cfg_attr(feature = "fancy", derive(Debug))]
struct Card { name: String }The print that exists only sometimes
Build with the feature on, and this print compiles and runs. Build without it, and the same line is an error — the trait was never derived:
let c = Card { name: String::from("ada") };
println!("card = {:?}", c);Compose the condition
The predicate can be any cfg expression. This struct earns Debug only on a unix machine in a debug build. Both are true here:
#[cfg_attr(all(unix, debug_assertions), derive(Debug))]
struct Stamp { n: u32 }
println!("stamp = {:?}", Stamp { n: 3 });Two attributes ride together
List several attributes after the condition and each one applies. In release builds, this function gains inline and a pass on dead-code warnings:
#[cfg_attr(not(debug_assertions), inline, allow(dead_code))]
fn helper() -> u32 { 7 }The Windows console trick
A whole-crate inner cfg_attr. On Windows it applies windows_subsystem, so a GUI program opens no console window. On this machine the condition is false, and the line vanishes without a trace:
#![cfg_attr(windows, windows_subsystem = "windows")]Example
#[cfg_attr(debug_assertions, derive(Debug, Clone))]
struct Settings {
volume: u8,
muted: bool,
}
#[cfg_attr(all(unix, debug_assertions), derive(Debug))]
struct Stamp {
n: u32,
}
fn main() {
let s = Settings { volume: 7, muted: false };
let copy = s.clone();
println!("settings = {:?}", copy);
println!("stamp = {:?}", Stamp { n: 3 });
println!("debug build = {}", cfg!(debug_assertions));
}Key points
- cfg_attr takes the condition first, then one or more attributes: every listed attribute applies, or none of them do
- A false condition removes the attribute before any checking.
- Code that then uses the missing trait fails to compile — which is both the point and the surprise
- Feature-gated derives are how big crates offer extras: the type stays plain until a user turns the feature on
- The inner form works too: #![cfg_attr(...)] at the top of a file applies its attribute to the whole crate
In this example, the program runs as a debug build, so both cfg_attr conditions hold. Settings earns Debug and Clone, so the clone prints in full. Stamp earns Debug from its composed condition: unix, and debug assertions on. Build the same code with --release and it refuses to compile: neither derive was applied, so the {:?} prints have nothing to call. The attributes were never there — that is cfg_attr working as designed. The Platform Modules lesson builds a whole pattern on cfg_attr picking a module's file.
Note
cfg_attr can even hand a module its file through the path attribute.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
In a release build, what happens to #[cfg_attr(debug_assertions, derive(Debug))] on a struct?