Conditional compilation in Rust allows you to include or exclude code based on compile-time conditions. This powerful feature enables cross-platform development, feature toggling, and build optimization through the cfg attribute system.
Using conditional compilation, you can write code that adapts to different target platforms, optional features, and build configurations while maintaining a single codebase.
The cfg Attribute
The cfg attribute is the primary mechanism for conditional compilation in Rust. It allows you to conditionally compile items based on configuration options.
// Conditional compilation based on target OS
#[cfg(target_os = "windows")]
fn get_config_dir() -> PathBuf {
PathBuf::from(r"C:\ProgramData\MyApp")
}
#[cfg(target_os = "macos")]
fn get_config_dir() -> PathBuf {
PathBuf::from("/Library/Application Support/MyApp")
}
#[cfg(target_os = "linux")]
fn get_config_dir() -> PathBuf {
let home = env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
PathBuf::from(format!("{}/.config/myapp", home))
}
// Usage remains the same across platforms
fn main() {
let config_dir = get_config_dir();
println!("Config directory: {:?}", config_dir);
}
Platform-Specific Code Generation
#[cfg(target_os = "{OS}")] fn {name}()
→
Platform-specific function implementations
#[cfg(target_os = "windows")] fn get_temp_dir()
→
Windows-specific temp directory implementation
cfg! Macro
The cfg! macro evaluates configuration conditions at runtime, returning a boolean value:
fn main() {
// Check configuration at runtime
if cfg!(target_os = "windows") {
println!("Running on Windows");
} else if cfg!(target_os = "macos") {
println!("Running on macOS");
} else if cfg!(target_os = "linux") {
println!("Running on Linux");
}
// Combine multiple conditions
if cfg!(all(target_arch = "x86_64", target_os = "linux")) {
println!("Running on 64-bit Linux");
}
// Check for debug vs release builds
if cfg!(debug_assertions) {
println!("Debug build with assertions enabled");
} else {
println!("Release build");
}
}
Built-in Configuration Options
Rust provides many built-in configuration options that you can use for conditional compilation:
| Configuration | Description | Example Values |
|---|---|---|
target_arch |
Target CPU architecture | x86, x86_64, arm, aarch64 |
target_os |
Target operating system | windows, macos, linux, android |
target_family |
Operating system family | unix, windows |
target_env |
Target environment ABI | gnu, msvc, musl |
target_endian |
Target endianness | little, big |
target_pointer_width |
Target pointer width in bits | 16, 32, 64 |
debug_assertions |
Debug assertions enabled | true, false |
test |
Building for tests | true, false |
Feature Flags
Feature flags allow you to optionally enable or disable parts of your code based on Cargo features:
# Cargo.toml
[package]
name = "my-crate"
version = "0.1.0"
[features]
default = ["json-support"]
json-support = ["serde", "serde_json"]
xml-support = ["quick-xml"]
async-support = ["tokio"]
full = ["json-support", "xml-support", "async-support"]
[dependencies]
serde = { version = "1.0", optional = true }
serde_json = { version = "1.0", optional = true }
quick-xml = { version = "0.22", optional = true }
tokio = { version = "1.0", optional = true }
// Use feature flags in code
#[cfg(feature = "json-support")]
use serde_json;
#[cfg(feature = "json-support")]
pub fn parse_json(input: &str) -> Result {
serde_json::from_str(input)
}
#[cfg(feature = "xml-support")]
pub fn parse_xml(input: &str) -> Result {
// XML parsing implementation
}
#[cfg(feature = "async-support")]
pub async fn async_process(data: Vec) -> Result {
// Async processing implementation
tokio::time::sleep(Duration::from_millis(100)).await;
Ok(ProcessedData::new(data))
}
// Provide fallback for missing features
#[cfg(not(feature = "json-support"))]
pub fn parse_json(_input: &str) -> Result<(), &'static str> {
Err("JSON support not enabled")
}
Boolean Logic in cfg
The cfg attribute supports boolean logic for complex conditions:
// AND logic - all conditions must be true
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn linux_x64_specific() {
println!("This only runs on 64-bit Linux");
}
// OR logic - any condition can be true
#[cfg(any(target_os = "windows", target_os = "macos"))]
fn desktop_only() {
println!("This runs on desktop platforms");
}
// NOT logic - condition must be false
#[cfg(not(target_os = "windows"))]
fn non_windows() {
println!("This runs on everything except Windows");
}
// Complex combinations
#[cfg(all(
any(target_os = "linux", target_os = "macos"),
target_arch = "x86_64",
feature = "advanced-features"
))]
fn complex_condition() {
println!("Complex conditional compilation");
}
Custom Configuration Values
You can define custom configuration values using --cfg flags or build scripts:
Command Line Configuration
# Set custom cfg values via command line
cargo build --cfg custom_config
cargo build --cfg 'feature="experimental"'
# Multiple configurations
cargo build --cfg debug_mode --cfg 'version="1.0"'
Build Script Configuration
// build.rs
use std::env;
fn main() {
// Set configuration based on environment
if env::var("ENABLE_EXPERIMENTAL").is_ok() {
println!("cargo:rustc-cfg=experimental");
}
// Conditional configuration based on target
let target = env::var("TARGET").unwrap();
if target.contains("embedded") {
println!("cargo:rustc-cfg=embedded_target");
}
// Version-based configuration
let version = env::var("CARGO_PKG_VERSION").unwrap();
if version.starts_with("2.") {
println!("cargo:rustc-cfg=version_2");
}
}
// Use custom configuration in code
#[cfg(experimental)]
pub fn experimental_feature() -> Result {
// Cutting-edge functionality
Ok("Experimental result".to_string())
}
#[cfg(embedded_target)]
const BUFFER_SIZE: usize = 512; // Small buffer for embedded
#[cfg(not(embedded_target))]
const BUFFER_SIZE: usize = 8192; // Large buffer for desktop
#[cfg(version_2)]
pub fn new_api() -> ApiV2 {
ApiV2::new()
}
#[cfg(not(version_2))]
pub fn new_api() -> ApiV1 {
ApiV1::new()
}
Conditional Imports and Dependencies
Conditional compilation can be used with imports and dependencies:
// Conditional imports
#[cfg(target_family = "unix")]
use std::os::unix::fs::PermissionsExt;
#[cfg(target_os = "windows")]
use std::os::windows::fs::MetadataExt;
// Conditional module declarations
#[cfg(feature = "networking")]
pub mod network;
#[cfg(feature = "database")]
pub mod db;
// Platform-specific implementations
#[cfg(target_os = "linux")]
mod platform {
pub fn get_system_info() -> String {
std::fs::read_to_string("/proc/version")
.unwrap_or_else(|_| "Unknown".to_string())
}
}
#[cfg(target_os = "windows")]
mod platform {
pub fn get_system_info() -> String {
"Windows System".to_string()
}
}
pub use platform::get_system_info;
Testing with Conditional Compilation
Conditional compilation is particularly useful for testing scenarios:
// Test-only code
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_functionality() {
assert_eq!(add(2, 3), 5);
}
// Platform-specific tests
#[cfg(target_os = "linux")]
#[test]
fn test_linux_specific() {
// Linux-only test
}
}
// Debug-only functionality
#[cfg(debug_assertions)]
pub fn debug_info() -> String {
format!("Debug build at {}", file!())
}
// Mock implementations for testing
#[cfg(test)]
pub fn get_data() -> Vec {
vec![1, 2, 3, 4, 5] // Test data
}
#[cfg(not(test))]
pub fn get_data() -> Vec {
// Real implementation
std::fs::read("data.bin").unwrap_or_default()
}
Best Practices
Design Guidelines
- Minimize Platform Differences: Use conditional compilation sparingly
- Consistent APIs: Keep the same function signatures across platforms
- Fallback Implementations: Provide defaults when features are disabled
- Test All Configurations: Ensure all conditional branches are tested
Feature Flag Strategy
- Use descriptive feature names
- Group related functionality in features
- Provide sensible defaults
- Document feature dependencies clearly
🤖 For AI Coding Agents
Conditional Compilation Generation Instructions
When generating conditional code, follow these machine-readable specifications:
{
"conditional_compilation": {
"version": "1.0",
"cfg_attributes": {
"target_os": {
"values": ["windows", "macos", "linux", "android", "ios"],
"usage": "#[cfg(target_os = \"value\")]",
"purpose": "Platform-specific code compilation"
},
"target_arch": {
"values": ["x86", "x86_64", "arm", "aarch64"],
"usage": "#[cfg(target_arch = \"value\")]",
"purpose": "Architecture-specific optimizations"
},
"feature": {
"values": ["user_defined"],
"usage": "#[cfg(feature = \"feature_name\")]",
"purpose": "Optional feature compilation"
},
"test": {
"values": ["true", "false"],
"usage": "#[cfg(test)]",
"purpose": "Test-only code inclusion"
}
},
"boolean_operators": {
"all": "Logical AND - all conditions must be true",
"any": "Logical OR - any condition can be true",
"not": "Logical NOT - condition must be false"
},
"template_patterns": {
"platform_specific_function": {
"pattern": "#[cfg(target_os = \"{OS}\")]\nfn {function_name}() -> {return_type} {\n // {OS}-specific implementation\n}",
"variables": ["OS", "function_name", "return_type"],
"example": "#[cfg(target_os = \"linux\")]\nfn get_config_path() -> PathBuf {\n PathBuf::from(\"/etc/myapp\")\n}"
}
}
}
}
Code Generation Templates
#[cfg(target_os = "{OS}")]\nfn {func}() {{ {impl} }}
→
#[cfg(target_os = "linux")]\nfn get_home_dir() {{ env::var("HOME") }}
{
"validation_rules": {
"platform_coverage": "All major platforms should have implementations",
"fallback_required": "Always provide not() fallback for missing cases",
"consistent_apis": "Function signatures must match across platforms",
"feature_dependencies": "Document all feature flag dependencies"
},
"common_patterns": {
"cross_platform_function": "Multiple cfg attributes for same function",
"feature_optional_module": "Module only available with feature flag",
"debug_only_code": "Code only in debug builds",
"test_mocks": "Different implementations for test vs production"
}
}
✅ Correct: Complete Platform Coverage
// ✅ VALID: All platforms covered with fallback
#[cfg(target_os = "windows")]
fn get_config_dir() -> PathBuf {
PathBuf::from(r"C:\ProgramData\MyApp")
}
#[cfg(target_os = "macos")]
fn get_config_dir() -> PathBuf {
PathBuf::from("/Library/Application Support/MyApp")
}
#[cfg(target_os = "linux")]
fn get_config_dir() -> PathBuf {
PathBuf::from("/etc/myapp")
}
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
fn get_config_dir() -> PathBuf {
PathBuf::from("/tmp/myapp") // Generic fallback
}
❌ Incorrect: Missing Platform Coverage
// ❌ INVALID: Only covers some platforms
#[cfg(target_os = "linux")]
fn get_config_dir() -> PathBuf {
PathBuf::from("/etc/myapp")
}
#[cfg(target_os = "windows")]
fn get_config_dir() -> PathBuf {
PathBuf::from(r"C:\ProgramData")
}
// ❌ ERROR: What happens on macOS, BSD, etc.?
// This will cause compilation errors on unsupported platforms