๐Ÿฆ€ 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

774: Const Generics: fn<const N: usize> Fundamentals

Difficulty: 3 Level: Intermediate Parameterize types and functions with compile-time integers โ€” `Vector<3>` and `Vector<4>` are different types, and the compiler catches dimension mismatches before your code runs.

The Problem This Solves

Before const generics (stable since Rust 1.51), fixed-size arrays were awkward in generic code. You couldn't write `fn sum<const N: usize>(arr: &[i32; N]) -> i32` โ€” you had to use slices and lose the compile-time size information, or write separate implementations for each size. This matters for numerical code, embedded systems, and any domain where array dimensions have semantic meaning. A `Matrix<3, 4>` (3 rows, 4 columns) and a `Matrix<4, 3>` are fundamentally different. Multiplying two matrices requires the inner dimensions to match: `Matrix<A, B> Matrix<B, C>` is valid; `Matrix<A, B> Matrix<A, B>` is not (unless `A == B`). With const generics, wrong dimensions are a compile error, not a runtime panic. The same principle applies to fixed-size buffers in embedded systems, type-safe packet structures in networking, and statically-allocated data structures where heap allocation is forbidden.

The Intuition

Think of const generics as type parameters that are integers instead of types. `Vec<i32>` has a type parameter; `[i32; 8]` has a const parameter. Const generics generalize this: `fn zeros<const N: usize>() -> [i32; N]` is a single function that works for any array size, with the size checked at compile time. In C++, this is `template<size_t N>`. In Haskell and TypeScript, it's type-level natural numbers with more complexity. Rust makes it straightforward: `const N: usize` in angle brackets, used wherever a `usize` constant is expected.

How It Works in Rust

// A function that works on arrays of any compile-time-known size
fn sum<const N: usize>(arr: &[i32; N]) -> i32 {
 arr.iter().sum()
}

// Dot product โ€” both arrays must have the same N, enforced by the type
fn dot<const N: usize>(a: &[f64; N], b: &[f64; N]) -> f64 {
 a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}

// A type parameterized by size โ€” Vector<3> and Vector<4> are different types
#[derive(Debug, Clone, PartialEq)]
pub struct Vector<const N: usize> {
 data: [f64; N],
}

impl<const N: usize> Vector<N> {
 pub fn new(data: [f64; N]) -> Self { Self { data } }
 pub fn zeros() -> Self { Self { data: [0.0; N] } }

 pub fn dot(&self, other: &Self) -> f64 {
     // `other: &Self` means other must also be Vector<N> โ€” same size!
     self.data.iter().zip(other.data.iter()).map(|(a, b)| a * b).sum()
 }
}

// These all compile โ€” N is inferred from the argument
let a = [1, 2, 3];
let b = [4, 5, 6];
println!("{}", sum(&a));     // N = 3, inferred
println!("{}", dot(&a.map(|x| x as f64), &b.map(|x| x as f64)));

// These are DIFFERENT types โ€” the compiler won't mix them up
let v3: Vector<3> = Vector::new([1.0, 2.0, 3.0]);
let v4: Vector<4> = Vector::new([1.0, 2.0, 3.0, 4.0]);
// v3.dot(&v4)  โ† COMPILE ERROR: expected Vector<3>, found Vector<4>

// Reverse a fixed-size array โ€” returns the same type
fn reversed<T: Copy + Default, const N: usize>(arr: &[T; N]) -> [T; N] {
 let mut out = [T::default(); N];
 for i in 0..N {
     out[i] = arr[N - 1 - i];
 }
 out
}
Key points:

What This Unlocks

Key Differences

ConceptOCamlRust
Const genericsNo direct equivalent (GADTs approximate)`fn f<const N: usize>` โ€” stable since 1.51
Fixed array in generic`'a array` โ€” dynamic size, no static guarantee`[T; N]` โ€” size is part of the type
Type-level integersPeano encoding with GADTs`const N: usize` โ€” plain integer
Dimension mismatchRuntime checkCompile error โ€” different types
Type inferenceHindley-Milner infers universally`N` inferred from array argument
Multiple const paramsN/A`struct Matrix<const R: usize, const C: usize>`
// 774. Const Generics: fn<const N: usize> Fundamentals
// Stable since Rust 1.51

// โ”€โ”€ Functions parameterized by const โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

/// Sum an array of any compile-time-known length
fn sum<const N: usize>(arr: &[i32; N]) -> i32 {
    arr.iter().sum()
}

/// Dot product of two fixed-size arrays
fn dot<const N: usize>(a: &[f64; N], b: &[f64; N]) -> f64 {
    a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}

/// Zero-initialize a fixed-size array (compile-time size)
fn zeros<const N: usize>() -> [i32; N] {
    [0; N]
}

/// Check if two fixed-size arrays are equal element-wise
fn array_eq<T: PartialEq, const N: usize>(a: &[T; N], b: &[T; N]) -> bool {
    a == b
}

/// Reverse a fixed-size array
fn reversed<T: Copy + Default, const N: usize>(arr: &[T; N]) -> [T; N] {
    let mut out = [T::default(); N];
    for i in 0..N {
        out[i] = arr[N - 1 - i];
    }
    out
}

// โ”€โ”€ Type that carries size in the type system โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

#[derive(Debug, Clone, PartialEq)]
pub struct Vector<const N: usize> {
    data: [f64; N],
}

impl<const N: usize> Vector<N> {
    pub fn new(data: [f64; N]) -> Self { Self { data } }
    pub fn zeros() -> Self { Self { data: [0.0; N] } }

    pub fn norm_sq(&self) -> f64 {
        self.data.iter().map(|x| x * x).sum()
    }

    pub fn norm(&self) -> f64 {
        self.norm_sq().sqrt()
    }

    pub fn dot(&self, other: &Self) -> f64 {
        self.data.iter().zip(other.data.iter()).map(|(a, b)| a * b).sum()
    }
}

impl<const N: usize> std::fmt::Display for Vector<N> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "[")?;
        for (i, v) in self.data.iter().enumerate() {
            if i > 0 { write!(f, ", ")?; }
            write!(f, "{v:.2}")?;
        }
        write!(f, "]")
    }
}

fn main() {
    // Free functions with const generic
    let a3 = [1, 2, 3];
    let a4 = [1, 2, 3, 4];
    println!("sum([1,2,3])   = {}", sum(&a3));
    println!("sum([1,2,3,4]) = {}", sum(&a4));

    let fa = [1.0_f64, 2.0, 3.0];
    let fb = [4.0_f64, 5.0, 6.0];
    println!("dot([1,2,3],[4,5,6]) = {}", dot(&fa, &fb)); // 32

    let z: [i32; 5] = zeros();
    println!("zeros:5  = {z:?}");

    let rev = reversed(&[1, 2, 3, 4, 5]);
    println!("reversed = {rev:?}");

    // Vector<N> type
    let v3 = Vector::new([3.0_f64, 4.0, 0.0]);
    println!("\nv3 = {v3}");
    println!("norm = {:.2}", v3.norm()); // 5.0

    let v2 = Vector::new([1.0_f64, 0.0]);
    let w2 = Vector::new([0.0_f64, 1.0]);
    println!("dot(e1, e2) = {}", v2.dot(&w2)); // 0 โ€” orthogonal
}

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

    #[test]
    fn sum_fixed() {
        assert_eq!(sum(&[1, 2, 3, 4, 5]), 15);
    }

    #[test]
    fn dot_product() {
        let a = [1.0_f64, 2.0, 3.0];
        let b = [4.0_f64, 5.0, 6.0];
        assert!((dot(&a, &b) - 32.0).abs() < 1e-10);
    }

    #[test]
    fn zeros_all_zero() {
        let z: [i32; 7] = zeros();
        assert!(z.iter().all(|&x| x == 0));
    }

    #[test]
    fn vector_norm() {
        let v = Vector::new([3.0_f64, 4.0]);
        assert!((v.norm() - 5.0).abs() < 1e-10);
    }

    #[test]
    fn reversed_array() {
        assert_eq!(reversed(&[1, 2, 3]), [3, 2, 1]);
    }
}
(* Const generics concept in OCaml
   OCaml doesn't have const generics directly โ€” we simulate with modules and phantom types *)

(* โ”€โ”€ Module-level "const" size โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ *)
module type SIZE = sig val n : int end

module Three : SIZE = struct let n = 3 end
module Four  : SIZE = struct let n = 4 end

(* โ”€โ”€ Fixed-size vector functor โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ *)
module FixedVec (S : SIZE) = struct
  type t = float array

  let create init = Array.make S.n init

  let sum v =
    assert (Array.length v = S.n);
    Array.fold_left ( +. ) 0.0 v

  let dot a b =
    assert (Array.length a = S.n && Array.length b = S.n);
    Array.init S.n (fun i -> a.(i) *. b.(i))
    |> Array.fold_left ( +. ) 0.0

  let size = S.n
end

module Vec3 = FixedVec(Three)
module Vec4 = FixedVec(Four)

let () =
  let a = [|1.0; 2.0; 3.0|] in
  let b = [|4.0; 5.0; 6.0|] in
  Printf.printf "Vec3 size: %d\n" Vec3.size;
  Printf.printf "sum a:     %.1f\n" (Vec3.sum a);
  Printf.printf "dot a.b:   %.1f\n" (Vec3.dot a b);  (* 1*4+2*5+3*6 = 32 *)

  let v4 = Vec4.create 1.0 in
  Printf.printf "Vec4 size: %d\n" Vec4.size;
  Printf.printf "sum v4:    %.1f\n" (Vec4.sum v4)