๐Ÿฆ€ Functional Rust

777: Compile-Time Assertions with const

Difficulty: 3 Level: Intermediate Use `const { assert!(...) }` to catch invalid invariants at compile time โ€” zero runtime overhead, build fails instead.

The Problem This Solves

Some invariants are best caught at compile time. If your wire protocol requires `PacketHeader` to be exactly 16 bytes, you want the build to fail โ€” not a runtime panic โ€” when someone adds a field and breaks that guarantee. If a const generic parameter must be a power of two (for bitwise modulo to work), you want the compiler to tell you immediately, not after your ring buffer silently produces wrong results. `const` assertions accomplish this: the expression is evaluated during compilation, and if it's false, compilation fails with your custom message. Unlike `#[static_assert]` in C++, Rust's `const { assert!(...) }` is first-class syntax that works anywhere a `const` block is valid. Combined with `std::mem::size_of` and `std::mem::align_of`, you can express ABI and layout guarantees that survive any future refactoring. This pattern is used extensively in embedded Rust (ensuring structs have the right layout for hardware registers), networking (wire format compatibility), and performance-critical code (alignment for SIMD, power-of-two for fast modulo).

The Intuition

`const _: () = assert!(condition, "message");` evaluates `condition` at compile time. If false, the build fails with "message". Use it for: struct size/alignment guarantees (`size_of::<T>() == N`), const parameter validation (`N.is_power_of_two()`), domain invariants (protocol version > 0), and interop requirements (FFI struct layout). The `const fn` variant lets you validate at the point of instantiation in generic code.

How It Works in Rust

use std::mem::{size_of, align_of};

// Basic compile-time size assertions
const _: () = assert!(size_of::<u64>() == 8, "u64 must be 8 bytes");
const _: () = assert!(usize::BITS >= 32,     "need at least 32-bit usize");

// Wire format: this struct MUST be exactly 16 bytes
#[repr(C)]
pub struct PacketHeader {
 pub magic:   u32,   // 4 bytes
 pub version: u8,    //   1 byte
 pub flags:   u8,    //   1 byte
 pub length:  u16,   //   2 bytes
 pub seq:     u64,   //   8 bytes = 16 total
}
const _: () = assert!(
 size_of::<PacketHeader>() == 16,
 "PacketHeader must be exactly 16 bytes for wire compatibility"
);

// const fn: validate at point of use in generic context
const fn must_be_power_of_two(n: usize) -> usize {
 assert!(n.is_power_of_two(), "N must be a power of two");
 n
}
const CACHE_SIZE: usize = must_be_power_of_two(1024);  // ok
// const BAD: usize = must_be_power_of_two(1000);       // compile error!

// Generic struct with inline validation
pub struct BoundedVec<T, const MAX: usize> {
 data: Vec<T>,
}
impl<T, const MAX: usize> BoundedVec<T, MAX> {
 const _CHECK: () = assert!(MAX > 0 && MAX <= 1_000_000, "MAX out of range");

 pub fn new() -> Self {
     let _ = Self::_CHECK;  // trigger the assert when new() is called in const context
     Self { data: Vec::new() }
 }
}
`#[repr(C)]` is required for layout guarantees โ€” without it, Rust may reorder fields for optimization. The `const _: ()` pattern uses the unit type and the wildcard `_` to create anonymous compile-time checks without polluting the namespace.

What This Unlocks

Key Differences

ConceptOCamlRust
Compile-time assertionNo direct equivalent`const _: () = assert!(...)` โ€” first-class
Struct layout control`[@unboxed]`, `[@noalloc]``#[repr(C)]`, `#[repr(packed)]`, `#[repr(align(N))]`
Size of type`Obj.size` (runtime)`std::mem::size_of::<T>()` โ€” const, compile-time
Generic const validationType-level tricks`const _CHECK: () = assert!(...)` in impl block
// 777. Compile-Time Assertions with const
// Build fails if invariants are violated โ€” zero runtime overhead

use std::mem::{size_of, align_of};

// โ”€โ”€ Basic const assertions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

// These run at compile time โ€” if false, the build fails
const _: () = assert!(size_of::<u64>() == 8,  "u64 must be 8 bytes");
const _: () = assert!(size_of::<u32>() == 4,  "u32 must be 4 bytes");
const _: () = assert!(size_of::<bool>() == 1, "bool must be 1 byte");
const _: () = assert!(usize::BITS >= 32,      "need at least 32-bit usize");

// โ”€โ”€ Protocol / domain invariants โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

const MAGIC: u32  = 0xCAFEBABE;
const VERSION: u8 = 2;
const MAX_PAYLOAD: usize = 65536;
const HEADER_SIZE: usize = 16;

// Ensure header fits in one cache line (โ‰ค 64 bytes)
const _: () = assert!(HEADER_SIZE <= 64, "header must fit in a cache line");
// Ensure max payload is a power of two
const _: () = assert!(MAX_PAYLOAD.is_power_of_two(), "MAX_PAYLOAD must be power of 2");
// Version must be non-zero
const _: () = assert!(VERSION > 0, "version must be > 0");

// โ”€โ”€ Struct size assertions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

#[repr(C)]
#[derive(Debug)]
pub struct PacketHeader {
    pub magic:   u32,
    pub version: u8,
    pub flags:   u8,
    pub length:  u16,
    pub seq:     u64,
}

// Wire format guarantee: header is exactly 16 bytes
const _: () = assert!(
    size_of::<PacketHeader>() == 16,
    "PacketHeader must be exactly 16 bytes for wire compatibility"
);
const _: () = assert!(
    align_of::<PacketHeader>() >= 4,
    "PacketHeader alignment must be at least 4"
);

// โ”€โ”€ Generic const assertions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

/// A type that is only usable if its alignment is at least A
pub struct AlignedBuffer<T, const A: usize>
where
    [(); { assert!(align_of::<T>() >= A, "alignment requirement not met"); 0 }]: Sized,
{
    data: T,
}

impl<T, const A: usize> AlignedBuffer<T, A>
where
    [(); { assert!(align_of::<T>() >= A, "alignment requirement not met"); 0 }]: Sized,
{
    pub fn new(data: T) -> Self { Self { data } }
    pub fn get(&self) -> &T { &self.data }
}

// โ”€โ”€ const fn that panics on bad input โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

const fn must_be_power_of_two(n: usize) -> usize {
    assert!(n.is_power_of_two(), "N must be a power of two");
    n
}

const CACHE_SIZE: usize = must_be_power_of_two(1024); // fine
// const BAD_CACHE: usize = must_be_power_of_two(1000); // compile error!

// โ”€โ”€ Capacity bounds check โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

pub struct BoundedVec<T, const MAX: usize> {
    data: Vec<T>,
}

impl<T, const MAX: usize> BoundedVec<T, MAX> {
    const _CHECK: () = assert!(MAX > 0 && MAX <= 1_000_000, "MAX out of range");

    pub fn new() -> Self {
        let _ = Self::_CHECK;
        Self { data: Vec::new() }
    }

    pub fn push(&mut self, v: T) -> Result<(), &'static str> {
        if self.data.len() >= MAX { return Err("capacity exceeded"); }
        self.data.push(v);
        Ok(())
    }
    pub fn len(&self) -> usize { self.data.len() }
}

fn main() {
    println!("All compile-time assertions passed!");
    println!("PacketHeader size: {} bytes", size_of::<PacketHeader>());
    println!("CACHE_SIZE: {CACHE_SIZE}");

    let header = PacketHeader {
        magic: MAGIC, version: VERSION, flags: 0, length: 64, seq: 1,
    };
    println!("Header: {header:?}");

    let mut bvec: BoundedVec<i32, 3> = BoundedVec::new();
    for i in 0..3 { bvec.push(i).unwrap(); }
    println!("BoundedVec len: {}", bvec.len());
    println!("BoundedVec push(3): {:?}", bvec.push(99));
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn packet_header_size_is_16() {
        assert_eq!(size_of::<PacketHeader>(), 16);
    }

    #[test]
    fn bounded_vec_enforces_capacity() {
        let mut bv: BoundedVec<i32, 2> = BoundedVec::new();
        assert!(bv.push(1).is_ok());
        assert!(bv.push(2).is_ok());
        assert!(bv.push(3).is_err());
    }

    #[test]
    fn cache_size_is_correct() {
        assert_eq!(CACHE_SIZE, 1024);
        assert!(CACHE_SIZE.is_power_of_two());
    }
}
(* Compile-time assertion concept in OCaml
   OCaml can check some things at module load (top-level), giving load-time errors.
   Not truly compile-time, but serves a similar purpose. *)

(* โ”€โ”€ Module-level invariant checks โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ *)

(* Check that a magic constant is valid *)
let () =
  let magic = 0xCAFEBABE in
  if magic land 0xFFFF0000 <> 0xCAFE0000 then
    failwith "FATAL: invalid magic constant โ€” this is a build-time error"

(* Check platform word size *)
let () =
  if Sys.int_size < 63 then
    failwith "Requires 64-bit platform"

(* Phantom type trick to enforce invariants at the type level *)
type validated
type unvalidated
type 'state port = Port of int

let validate_port (Port n as p : unvalidated port) : validated port option =
  if n > 0 && n <= 65535 then Some (Port n : validated port) else None

let use_port (Port n : validated port) =
  Printf.printf "Using port %d (guaranteed valid by type)\n" n

let () =
  (* This is the OCaml analog: we catch invalid configs early *)
  let port = Port 8080 in
  match validate_port port with
  | Some vp -> use_port vp
  | None    -> failwith "invalid port"