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

076: Trait Objects

Difficulty: Intermediate Category: Traits & Polymorphism Concept: Dynamic dispatch with `dyn Trait` Key Insight: OCaml uses structural typing (duck typing for modules); Rust uses `dyn Trait` for runtime polymorphism with vtables.
// 076: Trait Objects โ€” dynamic dispatch with dyn Trait

use std::f64::consts::PI;

// Approach 1: Define trait and implementations
trait Shape {
    fn area(&self) -> f64;
    fn name(&self) -> &str;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { PI * self.radius * self.radius }
    fn name(&self) -> &str { "circle" }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 { self.width * self.height }
    fn name(&self) -> &str { "rectangle" }
}

// Approach 2: Using dyn Trait for polymorphism
fn describe(shape: &dyn Shape) -> String {
    format!("{} with area {:.2}", shape.name(), shape.area())
}

fn total_area(shapes: &[Box<dyn Shape>]) -> f64 {
    shapes.iter().map(|s| s.area()).sum()
}

// Approach 3: Returning trait objects
fn make_shape(kind: &str) -> Box<dyn Shape> {
    match kind {
        "circle" => Box::new(Circle { radius: 5.0 }),
        "rectangle" => Box::new(Rectangle { width: 3.0, height: 4.0 }),
        _ => Box::new(Circle { radius: 1.0 }),
    }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 5.0 }),
        Box::new(Rectangle { width: 3.0, height: 4.0 }),
    ];
    for s in &shapes {
        println!("{}", describe(s.as_ref()));
    }
    println!("Total area: {:.2}", total_area(&shapes));
}

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

    #[test]
    fn test_circle() {
        let c = Circle { radius: 5.0 };
        assert!((c.area() - 78.54).abs() < 0.01);
        assert_eq!(c.name(), "circle");
    }

    #[test]
    fn test_rectangle() {
        let r = Rectangle { width: 3.0, height: 4.0 };
        assert_eq!(r.area(), 12.0);
    }

    #[test]
    fn test_dyn_dispatch() {
        let shapes: Vec<Box<dyn Shape>> = vec![
            Box::new(Circle { radius: 5.0 }),
            Box::new(Rectangle { width: 3.0, height: 4.0 }),
        ];
        assert!((total_area(&shapes) - 90.54).abs() < 0.01);
    }

    #[test]
    fn test_make_shape() {
        let s = make_shape("circle");
        assert_eq!(s.name(), "circle");
    }
}
(* 076: Trait Objects โ€” OCaml objects for dynamic dispatch *)

(* Approach 1: OCaml object types *)
class virtual shape = object
  method virtual area : float
  method virtual name : string
end

class circle r = object
  inherit shape
  method area = Float.pi *. r *. r
  method name = "circle"
end

class rectangle w h = object
  inherit shape
  method area = w *. h
  method name = "rectangle"
end

(* Approach 2: Using objects polymorphically *)
let describe (s : shape) =
  Printf.sprintf "%s with area %.2f" s#name s#area

let total_area (shapes : shape list) =
  List.fold_left (fun acc s -> acc +. s#area) 0.0 shapes

(* Approach 3: First-class modules as alternative *)
module type SHAPE = sig
  val area : unit -> float
  val name : unit -> string
end

let make_circle r : (module SHAPE) =
  (module struct
    let area () = Float.pi *. r *. r
    let name () = "circle"
  end)

let describe_mod (module S : SHAPE) =
  Printf.sprintf "%s with area %.2f" (S.name ()) (S.area ())

(* Tests *)
let () =
  let c = new circle 5.0 in
  let r = new rectangle 3.0 4.0 in
  assert (abs_float (c#area -. 78.54) < 0.01);
  assert (r#area = 12.0);
  assert (c#name = "circle");
  let shapes = [c :> shape; r :> shape] in
  assert (abs_float (total_area shapes -. 90.54) < 0.01);
  let mc = make_circle 5.0 in
  let desc = describe_mod mc in
  assert (String.sub desc 0 6 = "circle");
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Core Insight

Trait objects (`dyn Trait`) enable runtime polymorphism. The compiler generates a vtable for method dispatch. This is Rust's equivalent of OCaml's first-class modules or object system.

OCaml Approach

  • Object system with structural subtyping
  • First-class modules for ad-hoc polymorphism
  • No explicit vtable โ€” runtime dispatch via method lookup

Rust Approach

  • `dyn Trait` behind a pointer (`Box<dyn Trait>`, `&dyn Trait`)
  • Vtable-based dispatch (two-pointer fat pointer)
  • Object safety rules: no generics, no `Self` in return position

Comparison Table

FeatureOCamlRust
Dynamic dispatchObjects / first-class modules`dyn Trait`
Pointer typeImplicit (GC)`Box<dyn T>` / `&dyn T`
Type erasureYesYes (via vtable)
OverheadMethod lookupFat pointer + vtable