Attributes and Conditional Compilation

#[cfg_attr(...)]

#[cfg_attr(predicate, attr1, attr2, ...)] (and its inner form #![cfg_attr(predicate, ...)]) conditionally applies one or more attributes to an item, module, or crate without removing the item itself when predicate is false.

Expansion Semantics

During attribute expansion:

  • If predicate evaluates to true, #[cfg_attr(predicate, attr1, attr2)] expands directly to #[attr1] #[attr2] on the target item.
  • If predicate evaluates to false, #[cfg_attr(predicate, attr1, attr2)] expands to nothing, leaving the target item untouched in the AST.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct User { ... }

  When `--features serde` is enabled:
    #[derive(Serialize, Deserialize)]
    pub struct User { ... }

  When `serde` feature is disabled:
    pub struct User { ... }   (The struct still exists; only the attribute is omitted!)

Four Common Idioms Using #[cfg_attr(...)]

1. Conditional Derive Macros (serde, arbitrary, clap)

Library crates frequently offer optional serde support behind a Cargo feature flag ([features] serde = ["dep:serde"]) using #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))].

2. Conditional #![no_std] Crates with an Optional std Feature

Crates that work in bare-metal #![no_std] environments by default—while unlocking std::io::Error or heap features when the "std" Cargo feature is enabled—place #![cfg_attr(not(feature = "std"), no_std)] at the top of src/lib.rs:

// Compiles as `#![no_std]` ONLY when the `"std"` feature is NOT enabled!
#![cfg_attr(not(feature = "std"), allow(dead_code))]

// Conditionally derive `Clone` and `Copy` on debug builds, or `PartialEq` always:
#[derive(Debug, PartialEq)]
#[cfg_attr(debug_assertions, derive(Clone, Copy))]
struct PacketId(u32);

// Conditionally set the file path of a submodule based on target OS:
#[cfg_attr(unix, allow(unused_macros))]
fn check_packet(id: PacketId) -> u32 {
    // In debug builds (where `Copy` was derived above), `id` is copied:
    let copy = id;
    copy.0
}

fn main() {
    let p = PacketId(101);
    println!("packet id: {}", check_packet(p));
}
packet id: 101

3. Conditional Module File Paths (#[cfg_attr(..., path = "...")])

Instead of declaring two separate #[cfg(unix)] mod imp; and #[cfg(windows)] mod imp; statements, a single mod sys; declaration can select its backing source file via #[cfg_attr]:

// Selects `src/unix_sys.rs` on Unix and `src/win_sys.rs` on Windows:
// #[cfg_attr(unix, path = "unix_sys.rs")]
// #[cfg_attr(windows, path = "win_sys.rs")]
// mod sys;

4. Conditional Documentation Annotations on Docs.rs (#[cfg_attr(docsrs, doc(cfg(...)))])

Crates that document which feature flag enables each API on docs.rs pass --cfg docsrs in [package.metadata.docs.rs] and annotate feature-gated items with #[cfg_attr(docsrs, doc(cfg(feature = "tls")))].