Rust’s #[cfg] attribute is your go-to for conditionally compiling code based on build targets or environment variables, but for enabling or disabling specific functionalities within your library or application, Cargo’s feature flags are where it’s at. They let users of your crate pick and choose what parts of your code they want to include at compile time, leading to smaller binaries and faster builds for those who don’t need everything.
Imagine you’re building a web framework. You might have optional features like TLS support, WebSocket handling, or integration with a specific database. Instead of forcing users to compile all of that code, you can expose these as features.
Here’s a Cargo.toml snippet demonstrating this:
[package]
name = "web_framework"
version = "0.1.0"
edition = "2021"
[features]
# A default set of features that are enabled when no specific features are requested.
default = ["tls", "websockets"]
# Enables TLS support.
tls = []
# Enables WebSocket support.
websockets = ["dep:openssl"] # This feature depends on the 'openssl' crate.
# Enables PostgreSQL support.
postgres = ["dep:tokio-postgres"]
[dependencies]
# Other dependencies...
openssl = { version = "0.10", optional = true }
tokio-postgres = { version = "0.7", optional = true }
In this example:
defaultspecifies that if a user doesn’t explicitly request any features,tlsandwebsocketswill be compiled.tlsis a simple feature that doesn’t require any external dependencies.websocketsdepends on theopensslcrate. Theoptional = truein the[dependencies]section is crucial; it tells Cargo that this dependency should only be compiled if a feature that requires it is enabled.postgresdepends ontokio-postgres, also marked as optional.
Now, in your Rust code, you can use these features to conditionally compile blocks of code.
// src/lib.rs
#[cfg(feature = "tls")]
mod tls_module {
pub fn enable_tls() {
println!("TLS enabled!");
// Code related to TLS setup...
}
}
#[cfg(feature = "websockets")]
mod websockets_module {
pub fn handle_websockets() {
println!("WebSockets enabled!");
// Code related to WebSocket handling...
}
}
#[cfg(feature = "postgres")]
mod postgres_module {
pub fn connect_to_postgres() {
println!("PostgreSQL enabled!");
// Code related to PostgreSQL connection...
}
}
pub fn initialize() {
#[cfg(feature = "tls")]
tls_module::enable_tls();
#[cfg(feature = "websockets")]
websockets_module::handle_websockets();
#[cfg(feature = "postgres")]
postgres_module::connect_to_postgres();
}
// Example of a function that might use a feature internally
#[cfg(feature = "websockets")]
pub fn process_ws_message(message: &str) {
println!("Processing WS message: {}", message);
// ... actual WS processing logic
}
// This function will only be compiled if the 'websockets' feature is enabled.
// Users can call this if they have the feature enabled.
#[cfg(feature = "websockets")]
pub fn start_websocket_server() {
println!("Starting WebSocket server...");
// ... server startup logic
}
When a user compiles your crate, they can specify which features they want:
- Default features:
cargo build(will enabletlsandwebsocketsbased on ourdefaultsetting). - Specific features:
cargo build --features "tls"(onlytlswill be compiled). - Multiple features:
cargo build --features "tls,postgres"(bothtlsandpostgreswill be compiled). - All features:
cargo build --features "tls,websockets,postgres"(all features enabled). - Disabling default features:
cargo build --no-default-features --features "postgres"(onlypostgreswill be compiled, andtlsandwebsocketsfrom thedefaultwill be excluded).
The real power comes from how Cargo manages dependencies. When you mark a dependency as optional = true in [dependencies] and then reference it within a feature like websockets = ["dep:openssl"], Cargo ensures openssl is only fetched and compiled if the websockets feature (or any other feature that depends on openssl) is activated. This granular control over dependencies is key to optimizing build times and binary sizes.
A common point of confusion is when a feature itself depends on another feature within the same crate. This isn’t directly supported by the Cargo.toml syntax for features. Instead, you’d typically structure your code such that enabling the dependent feature implicitly enables the prerequisite, or you’d require the user to explicitly enable both. For instance, if advanced_websockets depended on websockets, you’d write advanced_websockets = ["websockets", "dep:some_other_crate"] and ensure websockets itself is marked as optional = true in the dependencies if it has its own external dependencies.
The next thing you’ll likely want to explore is how to create mutually exclusive features, where enabling one feature automatically disables another.