Attributes and Conditional Compilation

Feature-Based Compilation

Feature-based compilation uses Cargo's [features] table in Cargo.toml together with #[cfg(feature = "...")] attributes in Rust source code to make optional dependencies, heavy subsystems, and serialization integrations opt-in at compile time.

Defining Features in Cargo.toml

In Cargo.toml, features are declared under [features]. The special feature named default lists the features enabled automatically unless a downstream user sets default-features = false:

[package]
name = "http-client"
version = "0.1.0"
edition = "2024"

[dependencies]
# Optional dependencies are not compiled unless activated by a feature:
serde = { version = "1.0", optional = true, features = ["derive"] }
flate2 = { version = "1.0", optional = true }

[features]
# Enabled by default:
default = ["compression"]

# Activates the optional `flate2` dependency using `dep:` syntax:
compression = ["dep:flate2"]

# Activates optional `serde` dependency:
json = ["dep:serde"]

# Meta-feature enabling all capabilities:
full = ["compression", "json"]

The Fundamental Rule: Features Must Be Strictly Additive!

When multiple crates in a dependency tree depend on your library with different feature sets (crate_a enables feature = "json", while crate_b enables feature = "compression"), Cargo unions all requested features into a single compiled copy of your library (--cfg feature="json" --cfg feature="compression").

Cargo Feature Unification Across a Dependency Graph:

         [ Binary App ]
          /          \
         v            v
    [crate_a]      [crate_b]
  (wants "json") (wants "compression")
         \            /
          v          v
    [http-client] Compiled ONCE with BOTH `feature = "json"` AND `feature = "compression"`!

Because Cargo unions features across the entire dependency graph:

  1. Enabling a feature must only ADD items, trait implementations, or capabilities—never remove or change the signature of existing public items!
  2. Avoid mutually exclusive features (such as backend-openssl vs. backend-rustls where enabling both fails to compile). If two features control a runtime default, define a clear precedence order when both are enabled:
pub struct Payload {
    pub raw: Vec<u8>,
}

impl Payload {
    pub fn new(raw: Vec<u8>) -> Self {
        Self { raw }
    }

    // Always available regardless of feature flags:
    pub fn len(&self) -> usize {
        self.raw.len()
    }

    // Strictly additive method unlocked when `feature = "hex"` is enabled:
    #[cfg(feature = "hex")]
    pub fn to_hex(&self) -> String {
        self.raw.iter().map(|b| format!("{:02x}", b)).collect()
    }
}

fn main() {
    let p = Payload::new(vec![0xDE, 0xAD, 0xBE, 0xEF]);
    println!("payload len = {} bytes", p.len());
}
payload len = 4 bytes

Cargo CLI Commands for Testing Feature Combinations

Command Effect
cargo test Compiles and tests with default features
cargo test --no-default-features Verifies the crate compiles when all default features are disabled
cargo test --all-features Verifies the crate compiles when every feature is enabled simultaneously
cargo test --features "json,compression" Enables specific features on top of default