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

384: dyn Trait and Fat Pointers

Difficulty: 3 Level: Advanced Runtime polymorphism via vtable dispatch โ€” heterogeneous collections and plugin architectures.

The Problem This Solves

Generics give you zero-cost polymorphism via monomorphization: the compiler generates one copy of the function per concrete type. That's fast, but it means all types must be known at compile time, and you can't mix different types in the same collection. Sometimes you need runtime polymorphism: a `Vec` of different shapes that all implement `Draw`. A plugin system where plugins are loaded at runtime. A callback system where handlers can be any type implementing a trait. That's what `dyn Trait` is for โ€” it erases the concrete type and dispatches through a vtable, exactly like virtual functions in C++ or interface values in Go. The cost: one extra pointer indirection per method call (vtable lookup), and the concrete type is lost (no monomorphization). The benefit: heterogeneous collections, object-safe trait dispatch, and no code bloat from monomorphization.

The Intuition

`Box<dyn Draw>` is a fat pointer: two machine words. The first points to the data. The second points to the vtable โ€” a struct of function pointers for every method in the `Draw` trait. Calling `shape.draw()` loads the function pointer from the vtable and calls it. This is exactly how C++ virtual dispatch works. Not all traits are object-safe โ€” `dyn Trait` requires that no method returns `Self`, takes `Self` by value, or has generic type parameters. If a trait isn't object-safe, the compiler tells you. Common fixes: add `where Self: Sized` to non-object-safe methods, or use `Box<dyn Any>` for type-erased values.

How It Works in Rust

trait Draw {
 fn draw(&self);
 fn bounding_box(&self) -> (f64, f64, f64, f64);
}

struct Circle { x: f64, y: f64, r: f64 }
struct Rect   { x: f64, y: f64, w: f64, h: f64 }

impl Draw for Circle {
 fn draw(&self) { println!("Circle at ({}, {})", self.x, self.y); }
 fn bounding_box(&self) -> (f64, f64, f64, f64) {
     (self.x - self.r, self.y - self.r, self.r*2.0, self.r*2.0)
 }
}

impl Draw for Rect {
 fn draw(&self) { println!("Rect at ({}, {})", self.x, self.y); }
 fn bounding_box(&self) -> (f64, f64, f64, f64) { (self.x, self.y, self.w, self.h) }
}

// Heterogeneous collection โ€” only possible with dyn
let shapes: Vec<Box<dyn Draw>> = vec![
 Box::new(Circle { x: 0.0, y: 0.0, r: 5.0 }),
 Box::new(Rect   { x: 1.0, y: 1.0, w: 3.0, h: 4.0 }),
];

for shape in &shapes {
 shape.draw(); // vtable dispatch
}

What This Unlocks

Key Differences

ConceptOCamlRust
Runtime polymorphismVariants + pattern match, or first-class modules`dyn Trait` with vtable dispatch
Object fileModule value (first-class)`Box<dyn Trait>` fat pointer
Virtual dispatchAutomatic via GADT / records of functionsExplicit `dyn` keyword, compiler checks object safety
PerformanceRecord-of-functions has same overheadOne indirection per call via vtable
// dyn Trait and fat pointers in Rust
use std::fmt;

trait Animal: fmt::Display {
    fn speak(&self) -> String;
    fn name(&self) -> &str;
}

struct Dog { name: String }
struct Cat { name: String }
struct Parrot { name: String, word: String }

impl Animal for Dog {
    fn speak(&self) -> String { "Woof!".to_string() }
    fn name(&self) -> &str { &self.name }
}

impl Animal for Cat {
    fn speak(&self) -> String { "Meow!".to_string() }
    fn name(&self) -> &str { &self.name }
}

impl Animal for Parrot {
    fn speak(&self) -> String { format!("{}!", self.word) }
    fn name(&self) -> &str { &self.name }
}

impl fmt::Display for Dog { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Dog({})", self.name) } }
impl fmt::Display for Cat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Cat({})", self.name) } }
impl fmt::Display for Parrot { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Parrot({})", self.name) } }

fn make_noise(animals: &[Box<dyn Animal>]) {
    for animal in animals {
        println!("{} says: {}", animal.name(), animal.speak());
    }
}

fn largest_name<'a>(animals: &'a [Box<dyn Animal>]) -> &'a str {
    animals.iter().map(|a| a.name()).max_by_key(|n| n.len()).unwrap_or("")
}

fn main() {
    let animals: Vec<Box<dyn Animal>> = vec![
        Box::new(Dog { name: "Rex".to_string() }),
        Box::new(Cat { name: "Whiskers".to_string() }),
        Box::new(Parrot { name: "Polly".to_string(), word: "Hello".to_string() }),
    ];

    make_noise(&animals);

    // Fat pointer size = 2 * usize
    println!("\nSize of Box<dyn Animal>: {} bytes", std::mem::size_of::<Box<dyn Animal>>());
    println!("Size of Box<Dog>:        {} bytes", std::mem::size_of::<Box<Dog>>());
    println!("Longest name: {}", largest_name(&animals));
}

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

    #[test]
    fn test_dynamic_dispatch() {
        let a: Box<dyn Animal> = Box::new(Dog { name: "Buddy".to_string() });
        assert_eq!(a.speak(), "Woof!");
        assert_eq!(a.name(), "Buddy");
    }

    #[test]
    fn test_fat_pointer_size() {
        // fat pointer = 2 * pointer size
        assert_eq!(std::mem::size_of::<Box<dyn Animal>>(),
                   2 * std::mem::size_of::<usize>());
    }
}
(* dyn Trait (dynamic dispatch) in OCaml - polymorphism is the default *)

class type animal = object
  method speak : unit
  method name : string
end

class dog name_val = object
  method speak = Printf.printf "Woof!\n"
  method name = name_val
end

class cat name_val = object
  method speak = Printf.printf "Meow!\n"
  method name = name_val
end

class parrot name_val word = object
  method speak = Printf.printf "%s!\n" word
  method name = name_val
end

let make_noise (animals : animal list) =
  List.iter (fun a ->
    Printf.printf "%s says: " a#name;
    a#speak
  ) animals

let () =
  let animals : animal list = [
    new dog "Rex";
    new cat "Whiskers";
    new parrot "Polly" "Hello";
  ] in
  make_noise animals