Cargo and Package Management

Features

Cargo features ([features] in Cargo.toml) are named conditional-compilation flags that allow downstream consumers to opt into or out of optional functionality, extra trait implementations, or heavy dependencies. When Cargo builds a crate with a feature enabled, it passes --cfg feature="<name>" to rustc.

Cargo.toml:
  [features]
  default = ["std"]
  std = ["alloc"]
  alloc = []
  json = ["std"]
        |
        v  `cargo build --no-default-features --features alloc`
Compiler receives:
  `--cfg feature="alloc"`

Declaring and Using Features

[package]
name = "data_frame"
version = "0.1.0"
edition = "2024"

[features]
# Enabled automatically unless caller passes `--no-default-features`
default = ["std"]

# Enabling `std` automatically enables `alloc`
std = ["alloc"]
alloc = []
csv-export = ["std"]

Inside Rust source code, inspect active features with #[cfg(feature = "...")] or the boolean macro cfg!(feature = "..."):

pub struct Record {
    pub id: u64,
    pub label: &'static str,
}

impl Record {
    pub fn format_summary(&self) -> String {
        if cfg!(feature = "csv-export") {
            format!("{},{}", self.id, self.label)
        } else {
            format!("Record #{}: {}", self.id, self.label)
        }
    }
}

#[cfg(feature = "csv-export")]
pub fn csv_header() -> &'static str {
    "id,label"
}

fn main() {
    let rec = Record {
        id: 7,
        label: "sensor_alpha",
    };
    println!("{}", rec.format_summary());
}
Record #7: sensor_alpha

Command-Line Flags for Selecting Features

Command Active Features
cargo build Enables the default feature and everything listed in default = [...].
cargo build --features csv-export Enables default plus csv-export.
cargo build --no-default-features Disables default (useful for no_std embedded builds).
cargo build --no-default-features --features alloc Enables only alloc.
cargo test --all-features Enables every feature defined in [features].

Feature Unification and Why Features Must Be Additive

When multiple crates in a dependency graph depend on the same shared crate foo with different feature sets (for example, Crate A enables foo/json and Crate B enables foo/tls), Cargo unifies their features and compiles foo once with the union of all requested features (json + tls).

          [ Application ]
           /           \
          v             v
     [ Crate A ]   [ Crate B ]
     (foo/json)     (foo/tls)
           \           /
            v         v
        [ Shared Crate `foo` ]
   Compiled ONCE with `json` + `tls`!

Because of feature unification:

  1. Features must always be strictly additive: Enabling a feature may add new modules, functions, or trait implementations, but must never remove or change the signature of existing public items.
  2. Avoid mutually exclusive features (like backend-openssl vs backend-rustls that fail to compile when both are enabled at once), because cargo test --all-features or feature unification across two dependencies will enable both simultaneously. If a fallback priority is needed when both are enabled, select one deterministically via if cfg!(...) / else if cfg!(...).

Note that since Cargo Resolver v2 (standard in edition = "2021" and "2024"), features enabled only by [dev-dependencies] or [build-dependencies] are not unified into normal target builds unless those targets also request them.