ExamplesBy LevelBy TopicLearning Paths
777 Fundamental

777-const-assert-patterns — Const Assert Patterns

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "777-const-assert-patterns — Const Assert Patterns" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Runtime assertions (`assert!`) check invariants at program execution time — too late for configuration errors that could be caught earlier. Key difference from OCaml: 1. **Error timing**: Rust's `const_assert!` fails at compile time with a clear message; OCaml's `assert` fails at module initialization (still early, but not compile time).

Tutorial

The Problem

Runtime assertions (assert!) check invariants at program execution time — too late for configuration errors that could be caught earlier. Compile-time assertions (const_assert!) catch impossible configurations, wrong struct sizes, invalid constant values, and violated mathematical invariants during compilation. Used in embedded systems (verify that a packet type fits in a buffer), cryptography (check key size assumptions), and any code with compile-time-knowable invariants.

🎯 Learning Outcomes

  • • Implement const_assert!(cond) as a macro that evaluates a boolean constant at compile time
  • • Use const_assert_eq!(a, b) for equality checks on compile-time constants
  • • Validate struct sizes with const_assert_size!(Type, expected_bytes)
  • • Enforce configuration invariants: BUFFER_SIZE between MIN and MAX, power-of-two alignment
  • • Understand that failing const_assert! produces a compile error, not a runtime panic
  • Code Example

    #[macro_export]
    macro_rules! const_assert {
        ($cond:expr) => {
            const _: () = assert!($cond);
        };
    }
    
    const_assert!(BUFFER_SIZE.is_power_of_two());
    const_assert!(MIN < MAX);

    Key Differences

  • Error timing: Rust's const_assert! fails at compile time with a clear message; OCaml's assert fails at module initialization (still early, but not compile time).
  • Size assertions: std::mem::size_of::<T>() is a const fn in Rust; OCaml's Obj.size is runtime-only.
  • Macro system: Rust's macro_rules! can generate compile-time assertions; OCaml's ppx macros can generate code but not arbitrary compile-time checks.
  • Use in embedded: Rust's const_assert! is used in no_std firmware to verify buffer sizes without any runtime overhead; OCaml cannot target such environments.
  • OCaml Approach

    OCaml's module system allows some compile-time verification via module signatures. However, OCaml has no direct equivalent of const_assert. The closest is let () = assert (constant_expression) which evaluates at module initialization time, catching errors on first load. ppx_const provides conditional compilation. In practice, OCaml relies on type-level encoding (like phantom types with specific sizes) for compile-time invariants.

    Full Source

    #![allow(clippy::all)]
    //! # Const Assert Patterns
    //!
    //! Compile-time assertions and constraints.
    
    /// Static assertion that a condition holds at compile time
    #[macro_export]
    macro_rules! const_assert {
        ($cond:expr) => {
            const _: () = assert!($cond);
        };
        ($cond:expr, $msg:literal) => {
            const _: () = assert!($cond, $msg);
        };
    }
    
    /// Assert two values are equal at compile time
    #[macro_export]
    macro_rules! const_assert_eq {
        ($a:expr, $b:expr) => {
            const _: () = assert!($a == $b);
        };
    }
    
    /// Assert type size at compile time
    #[macro_export]
    macro_rules! const_assert_size {
        ($t:ty, $size:expr) => {
            const _: () = assert!(std::mem::size_of::<$t>() == $size);
        };
    }
    
    // Example usage of const assertions
    const_assert!(1 + 1 == 2);
    const_assert_eq!(2 * 3, 6);
    
    /// Validate configuration at compile time
    pub const MIN_BUFFER_SIZE: usize = 64;
    pub const MAX_BUFFER_SIZE: usize = 4096;
    pub const BUFFER_SIZE: usize = 256;
    
    const_assert!(BUFFER_SIZE >= MIN_BUFFER_SIZE);
    const_assert!(BUFFER_SIZE <= MAX_BUFFER_SIZE);
    const_assert!(BUFFER_SIZE.is_power_of_two());
    
    /// Type with size constraint
    #[repr(C)]
    pub struct Header {
        pub magic: [u8; 4],
        pub version: u32,
        pub flags: u32,
        pub reserved: u32,
    }
    
    const_assert_size!(Header, 16);
    
    /// Ensure alignment
    const_assert!(std::mem::align_of::<Header>() == 4);
    
    /// Validate enum discriminant size
    pub enum Status {
        Ok = 0,
        Error = 1,
        Pending = 2,
    }
    
    const_assert!(std::mem::size_of::<Status>() == 1);
    
    /// Compile-time computed constant with validation
    pub const fn validated_percentage(p: u8) -> u8 {
        assert!(p <= 100, "percentage must be <= 100");
        p
    }
    
    pub const DEFAULT_PERCENTAGE: u8 = validated_percentage(75);
    
    /// Compile-time bounds checking
    pub const fn safe_index<const N: usize>(idx: usize) -> usize {
        assert!(idx < N, "index out of bounds");
        idx
    }
    
    // This would fail to compile:
    // const BAD_INDEX: usize = safe_index::<5>(10);
    
    pub const GOOD_INDEX: usize = safe_index::<5>(3);
    
    /// Non-zero type at compile time
    pub const fn non_zero(n: u64) -> u64 {
        assert!(n != 0, "value must be non-zero");
        n
    }
    
    pub const DIVISOR: u64 = non_zero(42);
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_buffer_size_valid() {
            assert!(BUFFER_SIZE >= MIN_BUFFER_SIZE);
            assert!(BUFFER_SIZE <= MAX_BUFFER_SIZE);
        }
    
        #[test]
        fn test_header_size() {
            assert_eq!(std::mem::size_of::<Header>(), 16);
        }
    
        #[test]
        fn test_default_percentage() {
            assert_eq!(DEFAULT_PERCENTAGE, 75);
        }
    
        #[test]
        fn test_good_index() {
            assert_eq!(GOOD_INDEX, 3);
        }
    
        #[test]
        fn test_divisor() {
            assert_eq!(DIVISOR, 42);
        }
    
        // Compile-time tests
        const_assert!(MIN_BUFFER_SIZE < MAX_BUFFER_SIZE);
        const_assert!(DIVISOR > 0);
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_buffer_size_valid() {
            assert!(BUFFER_SIZE >= MIN_BUFFER_SIZE);
            assert!(BUFFER_SIZE <= MAX_BUFFER_SIZE);
        }
    
        #[test]
        fn test_header_size() {
            assert_eq!(std::mem::size_of::<Header>(), 16);
        }
    
        #[test]
        fn test_default_percentage() {
            assert_eq!(DEFAULT_PERCENTAGE, 75);
        }
    
        #[test]
        fn test_good_index() {
            assert_eq!(GOOD_INDEX, 3);
        }
    
        #[test]
        fn test_divisor() {
            assert_eq!(DIVISOR, 42);
        }
    
        // Compile-time tests
        const_assert!(MIN_BUFFER_SIZE < MAX_BUFFER_SIZE);
        const_assert!(DIVISOR > 0);
    }

    Deep Comparison

    OCaml vs Rust: Const Assert Patterns

    Compile-Time Assertions

    Rust

    #[macro_export]
    macro_rules! const_assert {
        ($cond:expr) => {
            const _: () = assert!($cond);
        };
    }
    
    const_assert!(BUFFER_SIZE.is_power_of_two());
    const_assert!(MIN < MAX);
    

    OCaml

    No compile-time assertions. Must use runtime:

    let () =
      if not (buffer_size land (buffer_size - 1) = 0) then
        failwith "buffer size must be power of two"
    

    Size Assertions

    Rust

    const_assert_size!(Header, 16);
    const_assert!(std::mem::align_of::<Data>() == 8);
    

    OCaml

    (* No compile-time size checks *)
    let () = assert (Obj.size (Obj.repr header) = 4)
    

    Validated Constants

    Rust

    pub const fn non_zero(n: u64) -> u64 {
        assert!(n != 0, "must be non-zero");
        n
    }
    
    pub const DIVISOR: u64 = non_zero(42); // Checked at compile time!
    

    Key Differences

    AspectOCamlRust
    Compile-time assertNoneconst { assert!() }
    Size checkRuntime onlyCompile-time
    Config validationModule initBefore compilation
    Error timingRuntimeCompile time

    Exercises

  • Write a const_assert_alignment!(Type, alignment) macro that checks std::mem::align_of::<Type>() == alignment and test it on types with known alignment requirements.
  • Use const_assert! to enforce that a PacketHeader struct fits in a 64-byte cache line (size_of::<PacketHeader>() <= 64).
  • Create a const_assert_range!(val, lo, hi) macro and use it to validate that all timeout constants in a configuration module are within acceptable bounds.
  • Open Source Repos