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

079: Associated Types

Difficulty: 2 Level: Intermediate Let a trait declare a placeholder type that each implementor fills in โ€” the implementor decides what type it produces, not the caller.

The Problem This Solves

When you implement `Iterator` for a custom type, you need to declare what type of values it yields. You could make this an extra type parameter: `trait Iterator<Item>` โ€” but then every function that accepts an iterator must carry `Item` in its signature too, and you'd need to write `fn sum<I: Iterator<i32>>` everywhere. Associated types solve this by binding the output type to the implementation. A `Counter` iterator always yields `u64`. That's not something the caller chooses โ€” it's a property of `Counter`. So `type Item = u64` lives inside the `impl Iterator for Counter` block, and callers just write `I: Iterator` without needing to track the item type separately. This is the same idea as OCaml module types: a module signature declares abstract type members that the implementing module fills in. The consumer of the module doesn't need to know the concrete type โ€” they just use whatever the module provides.

The Intuition

Type parameters say "the caller chooses." Associated types say "the implementor chooses."
// Extra type parameter โ€” CALLER must specify what Item is
trait BadIterator<Item> { fn next(&mut self) -> Option<Item>; }

// Associated type โ€” implementor specifies Item, caller doesn't need to
trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; }
In Python, this is roughly the difference between a function that takes an explicit type argument versus one that returns a type that's implicit from the object itself. In Java, it's like a generic interface where the type is fixed by the implementing class rather than left open.

How It Works in Rust

trait Container {
 type Item;            // implementor declares what the item type is

 fn push(&mut self, item: Self::Item);
 fn pop(&mut self) -> Option<Self::Item>;
}

struct Stack<T> { items: Vec<T> }

impl<T> Container for Stack<T> {
 type Item = T;        // Stack decides: its items are of type T

 fn push(&mut self, item: T) { self.items.push(item); }
 fn pop(&mut self) -> Option<T> { self.items.pop() }
}
// Multiple associated types โ€” input and output can differ
trait Transformer {
 type Input;
 type Output;
 fn transform(&self, input: Self::Input) -> Self::Output;
}

struct StringLength;
impl Transformer for StringLength {
 type Input = String;
 type Output = usize;
 fn transform(&self, input: String) -> usize { input.len() }
}

// Generic function: use T::Input and T::Output โ€” no extra type params needed
fn apply<T: Transformer>(t: &T, input: T::Input) -> T::Output {
 t.transform(input)
}

What This Unlocks

Key Differences

ConceptOCamlRust
Abstract type in interface`type t` in module signature`type Item` in trait
Implementor fills in typeModule provides `type t = ...``impl Trait for Foo { type Item = ... }`
Access from outside`M.t``T::Item` or `<T as Trait>::Item`
vs type parameterModule functor parameterExtra `<Item>` on the trait
Standard library use`Map.S` with `type key``Iterator` with `type Item`, `Add` with `type Output`
// Example 079: Associated Types
// OCaml module types โ†’ Rust associated types in traits

// === Approach 1: Trait with associated type ===
trait Container {
    type Item;

    fn empty() -> Self;
    fn push(&mut self, item: Self::Item);
    fn pop(&mut self) -> Option<Self::Item>;
    fn is_empty(&self) -> bool;
    fn size(&self) -> usize;
}

struct Stack<T> {
    items: Vec<T>,
}

impl<T> Container for Stack<T> {
    type Item = T;

    fn empty() -> Self {
        Stack { items: Vec::new() }
    }

    fn push(&mut self, item: T) {
        self.items.push(item);
    }

    fn pop(&mut self) -> Option<T> {
        self.items.pop()
    }

    fn is_empty(&self) -> bool {
        self.items.is_empty()
    }

    fn size(&self) -> usize {
        self.items.len()
    }
}

// === Approach 2: Associated type for output (like Add trait) ===
trait Combinable {
    type Output;
    fn combine(&self, other: &Self) -> Self::Output;
}

#[derive(Debug, Clone)]
struct Point {
    x: f64,
    y: f64,
}

impl Combinable for Point {
    type Output = f64; // distance between points
    fn combine(&self, other: &Self) -> f64 {
        ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
    }
}

impl Combinable for String {
    type Output = String;
    fn combine(&self, other: &Self) -> String {
        format!("{}{}", self, other)
    }
}

// === Approach 3: Multiple associated types ===
trait Transformer {
    type Input;
    type Output;
    fn transform(&self, input: Self::Input) -> Self::Output;
}

struct StringLength;

impl Transformer for StringLength {
    type Input = String;
    type Output = usize;
    fn transform(&self, input: String) -> usize {
        input.len()
    }
}

struct Doubler;

impl Transformer for Doubler {
    type Input = i32;
    type Output = i32;
    fn transform(&self, input: i32) -> i32 {
        input * 2
    }
}

// Generic function using associated types
fn apply_transform<T: Transformer>(t: &T, input: T::Input) -> T::Output {
    t.transform(input)
}

fn main() {
    // Container
    let mut stack: Stack<i32> = Container::empty();
    stack.push(1);
    stack.push(2);
    stack.push(3);
    println!("Stack size: {}", stack.size());
    println!("Popped: {:?}", stack.pop());

    // Combinable
    let p1 = Point { x: 0.0, y: 0.0 };
    let p2 = Point { x: 3.0, y: 4.0 };
    println!("Distance: {}", p1.combine(&p2));

    let s1 = "Hello, ".to_string();
    let s2 = "world!".to_string();
    println!("Combined: {}", s1.combine(&s2));

    // Transformer
    println!("Length: {}", apply_transform(&StringLength, "hello".into()));
    println!("Doubled: {}", apply_transform(&Doubler, 21));
}

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

    #[test]
    fn test_stack_operations() {
        let mut s: Stack<i32> = Container::empty();
        assert!(s.is_empty());
        s.push(10);
        s.push(20);
        assert_eq!(s.size(), 2);
        assert_eq!(s.pop(), Some(20));
        assert_eq!(s.pop(), Some(10));
        assert_eq!(s.pop(), None);
        assert!(s.is_empty());
    }

    #[test]
    fn test_string_stack() {
        let mut s: Stack<String> = Container::empty();
        s.push("hello".into());
        s.push("world".into());
        assert_eq!(s.pop(), Some("world".to_string()));
    }

    #[test]
    fn test_point_distance() {
        let p1 = Point { x: 0.0, y: 0.0 };
        let p2 = Point { x: 3.0, y: 4.0 };
        assert!((p1.combine(&p2) - 5.0).abs() < 1e-10);
    }

    #[test]
    fn test_string_combine() {
        let s1 = "foo".to_string();
        let s2 = "bar".to_string();
        assert_eq!(s1.combine(&s2), "foobar");
    }

    #[test]
    fn test_transformers() {
        assert_eq!(apply_transform(&StringLength, "test".into()), 4);
        assert_eq!(apply_transform(&Doubler, 5), 10);
    }
}
(* Example 079: Associated Types *)
(* OCaml module types โ†’ Rust associated types *)

(* Approach 1: Module type with associated type *)
module type Container = sig
  type t
  type item
  val empty : t
  val push : item -> t -> t
  val pop : t -> (item * t) option
  val is_empty : t -> bool
  val size : t -> int
end

(* Stack implementation *)
module Stack : Container with type item = int = struct
  type item = int
  type t = int list
  let empty = []
  let push x xs = x :: xs
  let pop = function [] -> None | x :: xs -> Some (x, xs)
  let is_empty = function [] -> true | _ -> false
  let size = List.length
end

(* Approach 2: Module type with type output *)
module type Addable = sig
  type t
  type output
  val add : t -> t -> output
end

module IntAdd : Addable with type t = int and type output = int = struct
  type t = int
  type output = int
  let add a b = a + b
end

module FloatAdd : Addable with type t = float and type output = float = struct
  type t = float
  type output = float
  let add a b = a +. b
end

(* Approach 3: Functor with associated output *)
module type Transformer = sig
  type input
  type output
  val transform : input -> output
end

module StringLen : Transformer with type input = string and type output = int = struct
  type input = string
  type output = int
  let transform = String.length
end

(* Tests *)
let () =
  let s = Stack.empty in
  let s = Stack.push 1 s in
  let s = Stack.push 2 s in
  let s = Stack.push 3 s in
  assert (Stack.size s = 3);
  assert (not (Stack.is_empty s));
  (match Stack.pop s with
   | Some (v, rest) ->
     assert (v = 3);
     assert (Stack.size rest = 2)
   | None -> assert false);

  assert (IntAdd.add 3 4 = 7);
  assert (FloatAdd.add 1.5 2.5 = 4.0);
  assert (StringLen.transform "hello" = 5);

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

๐Ÿ“Š Detailed Comparison

Comparison: Associated Types

Container with Associated Type

OCaml:

๐Ÿช Show OCaml equivalent
module type Container = sig
type t
type item
val empty : t
val push : item -> t -> t
val pop : t -> (item * t) option
end

module Stack : Container with type item = int = struct
type item = int
type t = int list
let empty = []
let push x xs = x :: xs
let pop = function [] -> None | x :: xs -> Some (x, xs)
end

Rust:

trait Container {
 type Item;
 fn empty() -> Self;
 fn push(&mut self, item: Self::Item);
 fn pop(&mut self) -> Option<Self::Item>;
}

impl<T> Container for Stack<T> {
 type Item = T;
 fn empty() -> Self { Stack { items: Vec::new() } }
 fn push(&mut self, item: T) { self.items.push(item); }
 fn pop(&mut self) -> Option<T> { self.items.pop() }
}

Transformer Pattern

OCaml:

๐Ÿช Show OCaml equivalent
module type Transformer = sig
type input
type output
val transform : input -> output
end

module StringLen : Transformer with type input = string and type output = int = struct
type input = string
type output = int
let transform = String.length
end

Rust:

trait Transformer {
 type Input;
 type Output;
 fn transform(&self, input: Self::Input) -> Self::Output;
}

impl Transformer for StringLength {
 type Input = String;
 type Output = usize;
 fn transform(&self, input: String) -> usize { input.len() }
}