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

382: Associated Types vs Type Parameters

Difficulty: 3 Level: Advanced Choose associated types when there's one natural output type; use type parameters when the caller decides.

The Problem This Solves

You're designing a trait that produces a value. Should the output type be a type parameter (`trait Convert<T>`) or an associated type (`trait Convert { type Output; }`)? This choice ripples through your API, your callers' type annotations, and what the compiler can infer. The rule of thumb: if an implementor can only convert to one target type meaningfully, use an associated type. If an implementor might convert to many different types and the caller controls which, use a type parameter. Getting this wrong means either multiple conflicting `impl` blocks or verbose type annotations that fight the compiler. This distinction matters deeply in trait design. `Iterator` uses `type Item` (associated) because each iterator produces one specific item type. A hypothetical `Into<T>` uses a type parameter because you can convert into many different types.

The Intuition

Associated type: the implementor decides what type is produced. `impl Iterator for MyStruct { type Item = u32; }` โ€” callers know `MyStruct::Item` is `u32` without specifying it. One impl per struct (can't implement `Iterator` twice). Type parameter: the caller decides what type is produced. `impl Into<String> for MyStruct` and `impl Into<Vec<u8>> for MyStruct` โ€” both can coexist. Callers must often annotate: `let x: String = val.into()`. Associated types also clean up bounds. Compare `fn process<I: Iterator<Item = u32>>(i: I)` vs `fn process<I: Iterator>(i: I) where I::Item: Display` โ€” the first is more common; the second shows how associated type projections work in where clauses.

How It Works in Rust

// Associated type โ€” implementor picks the output
trait Parse {
 type Output;
 type Error;
 fn parse(s: &str) -> Result<Self::Output, Self::Error>;
}

struct JsonParser;
impl Parse for JsonParser {
 type Output = serde_json::Value;
 type Error = serde_json::Error;
 fn parse(s: &str) -> Result<Self::Output, Self::Error> {
     serde_json::from_str(s)
 }
}

// Type parameter โ€” caller picks the output
trait ConvertTo<T> {
 fn convert(&self) -> T;
}

struct Rgb(u8, u8, u8);
impl ConvertTo<String> for Rgb {
 fn convert(&self) -> String { format!("#{:02X}{:02X}{:02X}", self.0, self.1, self.2) }
}
impl ConvertTo<u32> for Rgb {
 fn convert(&self) -> u32 { ((self.0 as u32) << 16) | ((self.1 as u32) << 8) | self.2 as u32 }
}

// Using associated type projection in bounds
fn sum_items<I>(iter: I) -> i32
where
 I: Iterator<Item = i32>,  // clean: Item is projected
{
 iter.sum()
}

What This Unlocks

Key Differences

ConceptOCamlRust
Associated typeModule type signature: `type t``trait Foo { type Bar; }`
Type parameter in traitFunctor parameter`trait Foo<T> { }`
Type projection`M.t` (module type member)`T::Item` (associated type projection)
Multiple implsFunctors with different modulesType parameter enables multiple `impl Foo<X> for T`
// Associated types vs type parameters in Rust
use std::fmt::Display;

// Associated type: one impl per type โ€” cleaner call sites
trait Container {
    type Item;  // associated type
    fn empty() -> Self;
    fn add(self, item: Self::Item) -> Self;
    fn to_vec(&self) -> Vec<Self::Item> where Self::Item: Clone;
}

// Type parameter: allows multiple impls for same type
trait ConvertTo<T> {
    fn convert(&self) -> T;
}

struct Stack<T>(Vec<T>);

impl<T: Clone> Container for Stack<T> {
    type Item = T;
    fn empty() -> Self { Stack(vec![]) }
    fn add(mut self, item: T) -> Self { self.0.push(item); self }
    fn to_vec(&self) -> Vec<T> { self.0.clone() }
}

// Multiple ConvertTo impls for the same type
struct Wrapper(i32);

impl ConvertTo<String> for Wrapper {
    fn convert(&self) -> String { self.0.to_string() }
}

impl ConvertTo<f64> for Wrapper {
    fn convert(&self) -> f64 { self.0 as f64 }
}

fn print_all<C: Container>(c: &C) where C::Item: Display + Clone {
    for item in c.to_vec() {
        print!("{} ", item);
    }
    println!();
}

fn main() {
    let s = Stack::<i32>::empty().add(1).add(2).add(3);
    print_all(&s);

    let w = Wrapper(42);
    let as_string: String = w.convert();
    let as_float: f64 = w.convert();
    println!("As string: {}, as float: {}", as_string, as_float);
}

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

    #[test]
    fn test_container() {
        let s = Stack::<i32>::empty().add(10).add(20);
        assert_eq!(s.to_vec(), vec![10, 20]);
    }

    #[test]
    fn test_convert_to() {
        let w = Wrapper(7);
        assert_eq!(ConvertTo::<String>::convert(&w), "7");
        assert_eq!((ConvertTo::<f64>::convert(&w) - 7.0).abs() < 1e-9, true);
    }
}
(* Associated types vs type parameters in OCaml *)

(* Associated type: one-to-one via module type *)
module type Container = sig
  type t
  type item  (* associated type *)
  val empty : t
  val add : item -> t -> t
  val to_list : t -> item list
end

(* Type parameter: flexible, multiple uses *)
module type Convertible = sig
  type t
  val convert : t -> string  (* fixed target type *)
end

module IntStack : Container with type item = int = struct
  type t = int list
  type item = int
  let empty = []
  let add x xs = x :: xs
  let to_list xs = xs
end

module StringStack : Container with type item = string = struct
  type t = string list
  type item = string
  let empty = []
  let add x xs = x :: xs
  let to_list xs = xs
end

let () =
  let s = IntStack.(add 3 (add 2 (add 1 empty))) in
  Printf.printf "IntStack: [%s]\n"
    (String.concat "; " (List.map string_of_int (IntStack.to_list s)));
  let ss = StringStack.(add "c" (add "b" (add "a" empty))) in
  Printf.printf "StringStack: [%s]\n"
    (String.concat "; " (StringStack.to_list ss))