ExamplesBy LevelBy TopicLearning Paths
419 Fundamental

419: `cfg!` and Conditional Compilation

Functional Programming

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

  • • Understand #[cfg(condition)] for item-level conditional compilation
  • • Learn cfg!() macro for inline conditional expressions
  • • See how target_os, target_arch, feature, debug_assertions are used
  • • Understand how #[cfg_attr(condition, attribute)] conditionally applies attributes
  • • Learn how cargo features enable/disable code paths via #[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

  • Granularity: Rust conditions can wrap individual items, expressions, or attributes; OCaml's conditional compilation is file-level.
  • Cargo integration: Rust #[cfg(feature = "x")] integrates directly with Cargo.toml's [features]; OCaml requires dune configuration.
  • Dead code elimination: Rust's compiler guarantees that #[cfg(not(...))] items are completely absent from the binary; OCaml's if Sys.os_type = "Unix" branches exist in the binary.
  • Attribute conditionals: Rust's #[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);
        }
    }
    ✓ Tests Rust test suite
    #[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.**
  • **Feature flags via Cargo.toml [features].**
  • OCaml uses preprocessor or dune configs.
  • Exercises

  • Platform utilities: Write a 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")].
  • Feature-gated struct: Define a 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.
  • Debug assertions: Implement a 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.
  • Open Source Repos