Rust provides conditional compilation through cfg attributes, allowing code to be compiled or excluded based on conditions like target platform, architecture, build mode, and Cargo features.
cfg vs. cfg_attr
The cfg attribute determines whether a block of code is included in compilation. The cfg_attr attribute, instead of directly enabling or disabling compilation, allows attaching additional attributes based on a condition.
#[cfg(debug_assertions)]
fn debug_mode() {
println!("Debug mode enabled.");
}
#[cfg_attr(test, allow(dead_code))]
fn test_function() {
println!("This function is compiled with test attributes.");
}In the example above, debug_mode is compiled only when debug assertions are enabled, while test_function allows dead code warnings to be silenced when running tests.
Common cfg Conditions
Rust provides several built-in cfg flags that can be used for conditional compilation.
Build Mode
debug_assertions: Enabled in debug mode, disabled in release builds.test: Enabled when runningcargo test.
Target Platform and Architecture
target_os = "linux"target_os = "windows"target_arch = "x86_64"target_pointer_width = "64"
These allow writing platform-specific implementations:
#[cfg(target_os = "windows")]
fn windows_specific() {
println!("This runs only on Windows.");
}
#[cfg(target_pointer_width = "64")]
fn only_64_bit() {
println!("Compiled only for 64-bit architectures.");
}Cargo Features
Cargo features allow enabling optional dependencies or experimental functionality.
#[cfg(feature = "serde_support")]
fn serialize_data() {
println!("Serialization feature is enabled.");
}To use this, define the feature in Cargo.toml:
[features]
serde_support = []Run it with cargo build --features serde_support.
Logical Combinations
Rust allows combining conditions with any, all, and not:
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn unix_specific() {
println!("Runs on Linux or macOS.");
}
#[cfg(all(target_os = "windows", target_env = "msvc"))]
fn windows_msvc() {
println!("Runs only on Windows with MSVC.");
}
#[cfg(not(debug_assertions))]
fn release_only() {
println!("This runs only in release mode.");
}Using cfg to Control Debugging
A useful trick is to enable additional debugging in test builds while keeping release builds clean:
#[cfg(debug_assertions)]
fn log_debug_info() {
println!("Debug logging enabled.");
}This allows enabling logging without affecting the performance of production builds.
Combining cfg with External Crates
When working with third-party crates like serde, cfg is useful to enable serialization only when necessary:
#[cfg(feature = "serde_support")]
use serde::{Serialize, Deserialize};
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
struct Data {
value: i32,
}This ensures serde is only included when explicitly enabled, preventing unnecessary dependencies.