๐Ÿฆ€ Functional Rust
๐ŸŽฌ Traits & Generics Shared behaviour, static vs dynamic dispatch, zero-cost polymorphism.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Traits define shared behaviour โ€” like interfaces but with default implementations

โ€ข Generics with trait bounds: fn process(item: T) โ€” monomorphized at compile time

โ€ข Static dispatch (impl Trait) = zero cost; dynamic dispatch (dyn Trait) = runtime flexibility via vtable

โ€ข Blanket implementations apply traits to all types matching a bound

โ€ข Associated types and supertraits enable complex type relationships

126: Const Generics

Difficulty: โญโญ Level: Intermediate Encode array sizes and matrix dimensions as type parameters so dimension mismatches become compile errors, not runtime panics.

The Problem This Solves

Imagine you're writing a linear algebra library. You have a `Matrix` type, and you want to multiply two matrices together. Matrix multiplication requires that the number of columns in the first matrix equals the number of rows in the second โ€” a `2ร—3` times a `3ร—4` gives a `2ร—4`, but `2ร—3` times `4ร—3` is illegal. In a typical Rust struct with `Vec<Vec<f64>>` inside, nothing stops you from passing the wrong shapes at runtime. You find out when your program panics โ€” or worse, silently produces wrong results. The same problem shows up with fixed-size arrays. A function that computes a dot product over two 3D vectors should reject two 4D vectors. Without const generics you'd either check at runtime (and handle the error) or use separate types `Vec3`, `Vec4`, etc. โ€” which doesn't scale. Const generics let you express "this struct holds exactly N elements" or "this matrix is M rows ร— N columns" directly in the type. The compiler checks dimensions at every call site. Pass a `Matrix<3,4>` where a `Matrix<4,3>` is expected? Compile error. No runtime cost, no boilerplate size-checking code.

The Intuition

Think of const generics as type parameters that carry numbers instead of types. Just as `Vec<T>` is generic over an element type `T`, `FixedArray<T, const N: usize>` is generic over both the element type and the length. `FixedArray<f64, 3>` and `FixedArray<f64, 4>` are completely different types โ€” you can't mix them up. Matrix multiplication makes this shine. The rule is: `Matrix<M, N>` ร— `Matrix<N, P>` = `Matrix<M, P>`. The inner dimension `N` must match. In Rust you express this as a method signature: `fn mul<const P: usize>(&self, other: &Matrix<T, N, P>) -> Matrix<T, M, P>`. The compiler sees that `other` must share the same `N` as `self`, and enforces it โ€” no runtime check required.

How It Works in Rust

// A fixed-size array where N is part of the type
#[derive(Debug, Clone)]
struct FixedArray<T, const N: usize> {
 data: [T; N],          // N is a const value, not a type โ€” but it lives in the type signature
}

impl<T: Default + Copy, const N: usize> FixedArray<T, N> {
 fn new(default: T) -> Self {
     FixedArray { data: [default; N] }  // [default; N] works because N is known at compile time
 }

 fn len(&self) -> usize {
     N  // the length is part of the type โ€” no need to store it
 }
}

// Dot product only compiles if both arrays have the same length N
impl<const N: usize> FixedArray<f64, N> {
 fn dot(&self, other: &Self) -> f64 {  // &Self means &FixedArray<f64, N> โ€” same N
     self.data.iter().zip(other.data.iter()).map(|(a, b)| a * b).sum()
 }
}

// Matrix: both dimensions are const generics
struct Matrix<T, const ROWS: usize, const COLS: usize> {
 data: [[T; COLS]; ROWS],
}

// Multiplication: compile-time dimension check
// self is Mร—N, other must be Nร—P, result is Mร—P
impl<T, const M: usize, const N: usize> Matrix<T, M, N>
where T: Default + Copy + std::ops::Add<Output = T> + std::ops::Mul<Output = T>
{
 fn mul<const P: usize>(&self, other: &Matrix<T, N, P>) -> Matrix<T, M, P> {
     // N in `self` must equal N in `other` โ€” the compiler verifies this
     let mut result = Matrix::<T, M, P>::new(T::default());
     // ...
     result
 }
}
Usage:
let m1: Matrix<f64, 2, 3> = Matrix::new(0.0);
let m2: Matrix<f64, 3, 2> = Matrix::new(0.0);
let m3: Matrix<f64, 2, 2> = m1.mul(&m2);  // compiles โ€” 3 matches 3

// let bad = m1.mul(&m1);   // compile error! Matrix<2,3> ร— Matrix<2,3> โ€” 3 โ‰  2

What This Unlocks

Key Differences

ConceptOCamlRust
Size as type paramFunctor `FixedArray(Size)` using a module`FixedArray<T, const N: usize>` โ€” value directly in the type
Matrix dimsRuntime check `failwith "dimension mismatch"`Compile-time: `Matrix<M,N>` ร— `Matrix<N,P>` enforced by the type checker
Const evaluationModule-level `let` computed at startup`const` items evaluated during compilation, embedded in binary
SyntaxModule system + functorsInline `const N: usize` parameter, same syntax as type generics
// Example 126: Const Generics
// Rust's const generics allow compile-time parameterization by values

use std::ops::{Add, Mul};

// Approach 1: Fixed-size array wrapper with const generic
#[derive(Debug, Clone)]
struct FixedArray<T, const N: usize> {
    data: [T; N],
}

impl<T: Default + Copy, const N: usize> FixedArray<T, N> {
    fn new(default: T) -> Self {
        FixedArray { data: [default; N] }
    }

    fn len(&self) -> usize {
        N
    }

    fn get(&self, i: usize) -> Option<&T> {
        self.data.get(i)
    }

    fn set(&mut self, i: usize, val: T) {
        if i < N {
            self.data[i] = val;
        }
    }

    fn map<U: Default + Copy, F: Fn(&T) -> U>(&self, f: F) -> FixedArray<U, N> {
        let mut result = FixedArray::<U, N>::new(U::default());
        for i in 0..N {
            result.data[i] = f(&self.data[i]);
        }
        result
    }
}

impl<const N: usize> FixedArray<f64, N> {
    fn dot(&self, other: &Self) -> f64 {
        self.data.iter().zip(other.data.iter()).map(|(a, b)| a * b).sum()
    }
}

// Approach 2: Matrix with const generics for rows and cols
#[derive(Debug, Clone)]
struct Matrix<T, const ROWS: usize, const COLS: usize> {
    data: [[T; COLS]; ROWS],
}

impl<T: Default + Copy, const ROWS: usize, const COLS: usize> Matrix<T, ROWS, COLS> {
    fn new(default: T) -> Self {
        Matrix {
            data: [[default; COLS]; ROWS],
        }
    }

    fn get(&self, r: usize, c: usize) -> Option<&T> {
        self.data.get(r).and_then(|row| row.get(c))
    }

    fn set(&mut self, r: usize, c: usize, val: T) {
        if r < ROWS && c < COLS {
            self.data[r][c] = val;
        }
    }

    fn rows(&self) -> usize { ROWS }
    fn cols(&self) -> usize { COLS }
}

// Matrix multiplication with compile-time dimension checking!
impl<T, const M: usize, const N: usize> Matrix<T, M, N>
where
    T: Default + Copy + Add<Output = T> + Mul<Output = T>,
{
    fn mul<const P: usize>(&self, other: &Matrix<T, N, P>) -> Matrix<T, M, P> {
        let mut result = Matrix::<T, M, P>::new(T::default());
        for i in 0..M {
            for j in 0..P {
                let mut sum = T::default();
                for k in 0..N {
                    sum = sum + self.data[i][k] * other.data[k][j];
                }
                result.data[i][j] = sum;
            }
        }
        result
    }
}

// Approach 3: Compile-time array operations
fn array_sum<const N: usize>(arr: &[f64; N]) -> f64 {
    arr.iter().sum()
}

fn array_zip_with<T: Copy, U: Copy, V, const N: usize>(
    a: &[T; N],
    b: &[U; N],
    f: impl Fn(T, U) -> V,
) -> Vec<V> {
    a.iter().zip(b.iter()).map(|(&x, &y)| f(x, y)).collect()
}

fn main() {
    // Const generic vectors
    let mut v: FixedArray<f64, 3> = FixedArray::new(0.0);
    v.set(0, 1.0);
    v.set(1, 2.0);
    v.set(2, 3.0);
    println!("Vec3: {:?}, len={}", v, v.len());

    let a = FixedArray { data: [1.0, 2.0, 3.0] };
    let b = FixedArray { data: [4.0, 5.0, 6.0] };
    println!("Dot product: {}", a.dot(&b));

    // Matrix multiplication with dimension safety
    let mut m1: Matrix<f64, 2, 3> = Matrix::new(0.0);
    m1.data = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
    let mut m2: Matrix<f64, 3, 2> = Matrix::new(0.0);
    m2.data = [[7.0, 8.0], [9.0, 10.0], [11.0, 12.0]];
    let m3: Matrix<f64, 2, 2> = m1.mul(&m2);
    println!("Matrix product [0][0]: {}", m3.data[0][0]);

    // Compile-time sized operations
    let arr = [1.0, 2.0, 3.0, 4.0, 5.0];
    println!("Sum of [1..5]: {}", array_sum(&arr));
}

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

    #[test]
    fn test_fixed_array_basic() {
        let mut v: FixedArray<i32, 4> = FixedArray::new(0);
        assert_eq!(v.len(), 4);
        v.set(2, 42);
        assert_eq!(v.get(2), Some(&42));
        assert_eq!(v.get(4), None);
    }

    #[test]
    fn test_dot_product() {
        let a = FixedArray { data: [1.0, 2.0, 3.0] };
        let b = FixedArray { data: [4.0, 5.0, 6.0] };
        assert_eq!(a.dot(&b), 32.0);
    }

    #[test]
    fn test_matrix_dimensions() {
        let m: Matrix<f64, 2, 3> = Matrix::new(0.0);
        assert_eq!(m.rows(), 2);
        assert_eq!(m.cols(), 3);
    }

    #[test]
    fn test_matrix_multiply() {
        let m1 = Matrix { data: [[1.0, 2.0], [3.0, 4.0]] };
        let m2 = Matrix { data: [[5.0, 6.0], [7.0, 8.0]] };
        let result: Matrix<f64, 2, 2> = m1.mul(&m2);
        assert_eq!(result.data[0][0], 19.0);
        assert_eq!(result.data[0][1], 22.0);
        assert_eq!(result.data[1][0], 43.0);
        assert_eq!(result.data[1][1], 50.0);
    }

    #[test]
    fn test_map() {
        let v = FixedArray { data: [1, 2, 3] };
        let doubled = v.map(|x| x * 2);
        assert_eq!(doubled.data, [2, 4, 6]);
    }

    #[test]
    fn test_array_sum() {
        assert_eq!(array_sum(&[1.0, 2.0, 3.0]), 6.0);
        assert_eq!(array_sum(&[10.0, 20.0]), 30.0);
    }
}
(* Example 126: Const Generics *)
(* OCaml doesn't have const generics โ€” we simulate fixed-size arrays with modules *)

(* Approach 1: Functorized fixed-size array *)
module type SIZE = sig
  val n : int
end

module FixedArray (S : SIZE) = struct
  type 'a t = 'a array

  let create default = Array.make S.n default

  let length _ = S.n

  let get arr i =
    if i < 0 || i >= S.n then failwith "index out of bounds"
    else arr.(i)

  let set arr i v =
    if i < 0 || i >= S.n then failwith "index out of bounds"
    else arr.(i) <- v

  let map f arr = Array.map f arr

  let dot a b =
    let sum = ref 0.0 in
    for i = 0 to S.n - 1 do
      sum := !sum +. a.(i) *. b.(i)
    done;
    !sum
end

module Size3 = struct let n = 3 end
module Vec3 = FixedArray(Size3)

(* Approach 2: Matrix with size encoding *)
module type MATRIX_SIZE = sig
  val rows : int
  val cols : int
end

module Matrix (S : MATRIX_SIZE) = struct
  type t = float array array

  let create default =
    Array.init S.rows (fun _ -> Array.make S.cols default)

  let get m r c = m.(r).(c)
  let set m r c v = m.(r).(c) <- v

  let rows = S.rows
  let cols = S.cols
end

module Mat2x3 = Matrix(struct let rows = 2 let cols = 3 end)

(* Approach 3: Simple tuple-based fixed vectors *)
type vec2 = { x: float; y: float }
type vec3 = { x: float; y: float; z: float }

let vec2_add a b = { x = a.x +. b.x; y = a.y +. b.y }
let vec3_add a b = { x = a.x +. b.x; y = a.y +. b.y; z = a.z +. b.z }
let vec3_dot a b = a.x *. b.x +. a.y *. b.y +. a.z *. b.z

(* Tests *)
let () =
  (* Test functorized array *)
  let v = Vec3.create 0.0 in
  Vec3.set v 0 1.0;
  Vec3.set v 1 2.0;
  Vec3.set v 2 3.0;
  assert (Vec3.length v = 3);
  assert (Vec3.get v 1 = 2.0);

  let a = [| 1.0; 2.0; 3.0 |] in
  let b = [| 4.0; 5.0; 6.0 |] in
  assert (Vec3.dot a b = 32.0);

  (* Test matrix *)
  let m = Mat2x3.create 0.0 in
  Mat2x3.set m 0 0 1.0;
  Mat2x3.set m 1 2 5.0;
  assert (Mat2x3.get m 0 0 = 1.0);
  assert (Mat2x3.get m 1 2 = 5.0);

  (* Test record-based vectors *)
  let v1 = { x = 1.0; y = 2.0; z = 3.0 } in
  let v2 = { x = 4.0; y = 5.0; z = 6.0 } in
  let sum = vec3_add v1 v2 in
  assert (sum.x = 5.0);
  assert (vec3_dot v1 v2 = 32.0);

  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Comparison: Const Generics

Fixed-Size Array

OCaml

๐Ÿช Show OCaml equivalent
module type SIZE = sig val n : int end

module FixedArray (S : SIZE) = struct
type 'a t = 'a array
let create default = Array.make S.n default
let length _ = S.n
let get arr i = arr.(i)
let dot a b =
 let sum = ref 0.0 in
 for i = 0 to S.n - 1 do
   sum := !sum +. a.(i) *. b.(i)
 done; !sum
end

module Size3 = struct let n = 3 end
module Vec3 = FixedArray(Size3)

Rust

struct FixedArray<T, const N: usize> {
 data: [T; N],
}

impl<const N: usize> FixedArray<f64, N> {
 fn dot(&self, other: &Self) -> f64 {
     self.data.iter()
         .zip(other.data.iter())
         .map(|(a, b)| a * b)
         .sum()
 }
}

let v: FixedArray<f64, 3> = FixedArray::new(0.0);

Matrix Multiplication (Compile-Time Dimension Check)

OCaml

๐Ÿช Show OCaml equivalent
(* No compile-time dimension check โ€” runtime only *)
let multiply m1 m2 =
if Array.length m1.(0) <> Array.length m2 then
 failwith "dimension mismatch"
else (* ... *)

Rust

// Compile-time! Matrix<M,N> * Matrix<N,P> โ†’ Matrix<M,P>
impl<T, const M: usize, const N: usize> Matrix<T, M, N> {
 fn mul<const P: usize>(&self, other: &Matrix<T, N, P>) -> Matrix<T, M, P> {
     // N must match โ€” compiler enforces this
     // ...
 }
}