#[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
predicateevaluates totrue,#[cfg_attr(predicate, attr1, attr2)]expands directly to#[attr1] #[attr2]on the target item. - If
predicateevaluates tofalse,#[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")))].