🎓 A1Learner
🔍

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?