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 running cargo 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.