419: `cfg!` and Conditional Compilation
Tutorial Video
Text description (accessibility)
This video demonstrates the "419: `cfg!` and Conditional Compilation" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Cross-platform libraries must support Linux, macOS, Windows, and embedded targets — each with different APIs, file paths, and system calls. Key difference from OCaml: 1. **Granularity**: Rust conditions can wrap individual items, expressions, or attributes; OCaml's conditional compilation is file
Tutorial
The Problem
Cross-platform libraries must support Linux, macOS, Windows, and embedded targets — each with different APIs, file paths, and system calls. Feature flags enable shipping a core library with optional capabilities that users opt into. Compiling debug-only code into release builds wastes binary space and performance. Conditional compilation solves all of these: #[cfg(target_os = "linux")] includes code only on Linux, #[cfg(feature = "advanced")] only when the feature is enabled, #[cfg(debug_assertions)] only in debug builds. The compiler eliminates excluded branches entirely — zero runtime cost.
#[cfg(...)] powers tokio's platform backends, std's OS-specific implementations, serde's feature-gated formats, and any multi-platform Rust library.
🎯 Learning Outcomes
#[cfg(condition)] for item-level conditional compilationcfg!() macro for inline conditional expressionstarget_os, target_arch, feature, debug_assertions are used#[cfg_attr(condition, attribute)] conditionally applies attributes#[cfg(feature = "name")]Code Example
// Compile-time condition check
if cfg!(target_os = "linux") {
// Linux-specific code
}
// Conditional compilation
#[cfg(feature = "logging")]
fn log(msg: &str) { println!("{}", msg); }
#[cfg(not(feature = "logging"))]
fn log(_: &str) {}
// Conditional attributes
#[cfg_attr(feature = "serde", derive(Serialize))]
struct Data { ... }Key Differences
#[cfg(feature = "x")] integrates directly with Cargo.toml's [features]; OCaml requires dune configuration.#[cfg(not(...))] items are completely absent from the binary; OCaml's if Sys.os_type = "Unix" branches exist in the binary.#[cfg_attr(condition, derive(Serialize))] conditionally applies attributes; OCaml has no equivalent.OCaml Approach
OCaml achieves conditional compilation through the dune build system. (libraries (select lib.ml from (linux -> linux_impl.ml) (windows -> windows_impl.ml))) selects platform-specific files. The Sys.os_type variable provides runtime OS detection. Feature flags are handled through dune's (flags ...) and C preprocessor #ifdef for C stubs. OCaml has no built-in cfg! equivalent — all conditional compilation is at the file/module level.
Full Source
#![allow(clippy::all)]
//! cfg! and Conditional Compilation
//!
//! Compile-time feature flags and platform-specific code.
/// Platform-specific path separator.
pub fn path_separator() -> char {
if cfg!(windows) {
'\\'
} else {
'/'
}
}
/// Debug-only function.
#[cfg(debug_assertions)]
pub fn debug_log(msg: &str) {
println!("[DEBUG] {}", msg);
}
#[cfg(not(debug_assertions))]
pub fn debug_log(_msg: &str) {
// No-op in release
}
/// Feature-gated functionality.
#[cfg(feature = "advanced")]
pub fn advanced_feature() -> &'static str {
"Advanced feature enabled"
}
#[cfg(not(feature = "advanced"))]
pub fn advanced_feature() -> &'static str {
"Advanced feature disabled"
}
/// OS-specific behavior.
pub fn os_name() -> &'static str {
#[cfg(target_os = "linux")]
return "Linux";
#[cfg(target_os = "macos")]
return "macOS";
#[cfg(target_os = "windows")]
return "Windows";
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
return "Unknown OS";
}
/// Architecture info.
pub fn arch_info() -> &'static str {
if cfg!(target_arch = "x86_64") {
"64-bit x86"
} else if cfg!(target_arch = "aarch64") {
"64-bit ARM"
} else if cfg!(target_arch = "x86") {
"32-bit x86"
} else {
"Other architecture"
}
}
/// Test-only utilities.
#[cfg(test)]
pub fn test_helper() -> i32 {
42
}
/// Conditional derive.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub struct Config {
pub name: String,
pub value: i32,
}
/// Platform-specific default.
impl Default for Config {
fn default() -> Self {
Config {
name: if cfg!(debug_assertions) {
"debug".to_string()
} else {
"release".to_string()
},
value: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path_separator() {
let sep = path_separator();
assert!(sep == '/' || sep == '\\');
}
#[test]
fn test_os_name() {
let name = os_name();
assert!(!name.is_empty());
}
#[test]
fn test_arch_info() {
let arch = arch_info();
assert!(!arch.is_empty());
}
#[test]
fn test_debug_assertions() {
// This test itself runs in debug mode
#[cfg(debug_assertions)]
assert!(true);
}
#[test]
fn test_config_default() {
let cfg = Config::default();
#[cfg(debug_assertions)]
assert_eq!(cfg.name, "debug");
}
#[test]
fn test_test_helper() {
assert_eq!(test_helper(), 42);
}
}#[cfg(test)]
pub fn test_helper() -> i32 {
42
}
/// Conditional derive.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub struct Config {
pub name: String,
pub value: i32,
}
/// Platform-specific default.
impl Default for Config {
fn default() -> Self {
Config {
name: if cfg!(debug_assertions) {
"debug".to_string()
} else {
"release".to_string()
},
value: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path_separator() {
let sep = path_separator();
assert!(sep == '/' || sep == '\\');
}
#[test]
fn test_os_name() {
let name = os_name();
assert!(!name.is_empty());
}
#[test]
fn test_arch_info() {
let arch = arch_info();
assert!(!arch.is_empty());
}
#[test]
fn test_debug_assertions() {
// This test itself runs in debug mode
#[cfg(debug_assertions)]
assert!(true);
}
#[test]
fn test_config_default() {
let cfg = Config::default();
#[cfg(debug_assertions)]
assert_eq!(cfg.name, "debug");
}
#[test]
fn test_test_helper() {
assert_eq!(test_helper(), 42);
}
}
Deep Comparison
OCaml vs Rust: Conditional Compilation
Rust cfg!
// Compile-time condition check
if cfg!(target_os = "linux") {
// Linux-specific code
}
// Conditional compilation
#[cfg(feature = "logging")]
fn log(msg: &str) { println!("{}", msg); }
#[cfg(not(feature = "logging"))]
fn log(_: &str) {}
// Conditional attributes
#[cfg_attr(feature = "serde", derive(Serialize))]
struct Data { ... }
OCaml Conditional Compilation
(* Using preprocessor *)
#ifdef DEBUG
let debug = true
#else
let debug = false
#endif
(* Or dune features *)
(* In dune file: (enabled_if (= %{profile} dev)) *)
5 Takeaways
cfg! returns bool at compile time.**#[cfg(...)] includes/excludes items.**#[cfg_attr(...)] adds conditional attributes.**Cargo.toml [features].**Exercises
platform_temp_dir() -> &'static str function returning "/tmp" on Unix and "C:\\Temp" on Windows using #[cfg(target_family = "unix")] and #[cfg(target_family = "windows")].MetricsCollector struct that is only compiled when feature "metrics" is enabled. For the non-metrics build, provide a zero-size stub with the same API that compiles away entirely.safe_divide(a: i32, b: i32) -> i32 that uses debug_assert!(b != 0) to catch division by zero in debug builds, panicking with a message. In release, skip the check for performance.