Features & Optional Dependencies
Cargo features are the standard way to compile optional code paths, wire optional dependencies, and let consumers pay only for what they enable.
Busca en todas las páginas de la documentación
Cargo features are the standard way to compile optional code paths, wire optional dependencies, and let consumers pay only for what they enable.
[features]
default = ["std"]
std = ["dep:log"]
metrics = ["dep:prometheus"]
[dependencies]
log = { version = "0.4", optional = true }
prometheus = { version = "0.13", optional = true }#[cfg(feature = "metrics")]
pub fn export_metrics() { /* ... */ }When to reach for this: Optional backends, platform-specific code, or trimming compile time for library consumers.
A library with database backends selected by feature:
[features]
default = ["postgres"]
postgres = ["dep:sqlx", "sqlx/postgres"]
sqlite = ["dep:sqlx", "sqlx/sqlite"]
[dependencies]
sqlx = { version = "0.8", optional = true, default-features = false }#[cfg(feature = "postgres")]
pub async fn connect(url: &str) -> sqlx::Result<sqlx::PgPool> {
sqlx::PgPool::connect(url).await
}
#[cfg(feature = "sqlite")]
pub async fn connect(url: &str) -> sqlx::Result<sqlx::SqlitePool> {
sqlx::SqlitePool::connect(url).await
}cargo build --no-default-features --features sqlite
cargo test --all-featuresWhat this demonstrates:
dep:sqlx syntax ties a feature directly to an optional crate--no-default-features and --all-features[features]
full = ["json", "metrics"]
json = ["dep:serde", "serde/derive"]full pulls in everything it listscfg in Code#[cfg(all(feature = "postgres", not(feature = "sqlite")))]
compile_error!("enable at most one database backend");feature, target_os, and custom cfg flagscompile_error! for mutually exclusive feature sets#[cfg(feature = "std")]
use std::collections::HashMap;
#[cfg(not(feature = "std"))]
use hashbrown::HashMap;#[cfg(test)] is separate from Cargo featurescfg_attr attaches attributes conditionallyrequired-features on [[example]] gates runnable demosdefault-features = false and explicit feature list.features = ["old"] breaks silently at compile time. Fix: treat feature renames as semver-major for libraries.pub + feature for every private helper creates API confusion. Fix: keep internal modules private; gate only public surface.--all-features and per-feature jobs.| Alternative | Use When | Don't Use When |
|---|---|---|
| Separate crates | Backends are large and mutually exclusive | Small optional 50-line module |
target_cfg only | OS-specific code without optional deps | User-selectable functionality |
cfg-if crate | Many nested cfg branches | Simple one-flag gates |
No. Features are compile-time only. Runtime toggles use config files or environment variables after building the right feature set.
my_crate = { version = "1", features = ["metrics"] } in their Cargo.toml.
dep:serde in a feature table explicitly enables optional dependency serde without enabling serde's own default features unless you also list them.
For libraries, yes when compile time matters. For apps, defaults can include everything you ship.
cargo test --no-default-features --features sqlite builds only that configuration.
Workspace [workspace.metadata] or shared feature names in [workspace.dependencies] keep naming consistent across members.
If two crates need incompatible versions of the same optional dep, split into separate workspace packages or use different package names via renaming (advanced).
List each feature in README and rustdoc with compile command examples. crates.io shows feature flags automatically from the manifest.
Treat them like public API for libraries. Renaming or removing features is a breaking change for consumers who enabled them.
build.rs can emit cargo:rustc-cfg=feature="foo" for detected platform capabilities, complementing manifest features.
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.
Revisado por Chris St. John·Última actualización: 16 jul 2026