777-const-assert-patterns — Const Assert Patterns
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
const_assert!(cond) as a macro that evaluates a boolean constant at compile timeconst_assert_eq!(a, b) for equality checks on compile-time constantsconst_assert_size!(Type, expected_bytes) BUFFER_SIZE between MIN and MAX, power-of-two alignmentconst_assert! produces a compile error, not a runtime panicCode 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
const_assert! fails at compile time with a clear message; OCaml's assert fails at module initialization (still early, but not compile time).std::mem::size_of::<T>() is a const fn in Rust; OCaml's Obj.size is runtime-only.macro_rules! can generate compile-time assertions; OCaml's ppx macros can generate code but not arbitrary compile-time checks.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);
}#[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
| Aspect | OCaml | Rust |
|---|---|---|
| Compile-time assert | None | const { assert!() } |
| Size check | Runtime only | Compile-time |
| Config validation | Module init | Before compilation |
| Error timing | Runtime | Compile time |
Exercises
const_assert_alignment!(Type, alignment) macro that checks std::mem::align_of::<Type>() == alignment and test it on types with known alignment requirements.const_assert! to enforce that a PacketHeader struct fits in a 64-byte cache line (size_of::<PacketHeader>() <= 64).const_assert_range!(val, lo, hi) macro and use it to validate that all timeout constants in a configuration module are within acceptable bounds.