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

077: Generic Bounds

Difficulty: 2 Level: Intermediate Constrain generic type parameters so a function can call trait methods on them โ€” more flexible than concrete types, more expressive than unconstrained generics.

The Problem This Solves

You want to write `find_max` that works for `i32`, `f64`, `String`, and any other comparable type โ€” without writing it three times. Unconstrained generics can't call any methods (Rust doesn't know what `T` can do). But if you write `T: PartialOrd`, you're promising "T can be compared" โ€” and Rust will accept your `if a >= b`. Without bounds, the compiler error is swift: `error[E0369]: binary operation >= cannot be applied to type T`. The fix is always the same: declare what you need from `T` upfront. This is fundamentally different from Python (which just tries the method at runtime and crashes if it's missing) or Java generics (which are erased and can only call `Object` methods without a bound). Bounds make implicit assumptions explicit and checked at compile time. Your function signature documents what it requires โ€” and the compiler enforces it.

The Intuition

`T: PartialOrd` means "T must implement the `PartialOrd` trait." Multiple bounds use `+`: `T: PartialOrd + Display` means "T must be both comparable and printable." Monomorphization means generic Rust code is as fast as hand-written concrete code โ€” no boxing, no vtable.

How It Works in Rust

// Single bound: T must support comparison
fn find_max<T: PartialOrd>(slice: &[T]) -> Option<&T> {
 slice.iter().reduce(|a, b| if a >= b { a } else { b })
}

// Multiple bounds: T must be both comparable AND printable
fn print_max<T: PartialOrd + Display>(slice: &[T]) -> Option<String> {
 find_max(slice).map(|v| format!("Max: {}", v))
}

// Clamp works for any ordered type โ€” floats, integers, strings
fn clamp<T: PartialOrd>(value: T, lo: T, hi: T) -> T {
 if value < lo { lo }
 else if value > hi { hi }
 else { value }
}
// Traits can require other traits as supertraits
trait Summarize: Display {          // anything that impls Summarize must also impl Display
 fn summary(&self) -> String;
}

fn print_summaries<T: Summarize>(items: &[T]) -> String {
 items.iter().map(|i| i.summary()).collect::<Vec<_>>().join(", ")
}

What This Unlocks

Key Differences

ConceptOCamlRust
Type constraints`'a. 'a -> 'a -> bool` inferred`<T: Trait>` explicit in signature
Multiple constraintsModule signatures, functors`T: Trait1 + Trait2`
DispatchCompile-time (parametric polymorphism)Compile-time (monomorphization)
SupertraitModule inclusion / functor composition`trait Foo: Bar + Baz`
Runtime costZeroZero (monomorphized to concrete types)
// Example 077: Generic Bounds
// OCaml type constraints โ†’ Rust <T: Trait> bounds

use std::fmt::Display;

// === Approach 1: Single trait bound ===
fn find_max<T: PartialOrd>(slice: &[T]) -> Option<&T> {
    slice.iter().reduce(|a, b| if a >= b { a } else { b })
}

fn find_min<T: PartialOrd>(slice: &[T]) -> Option<&T> {
    slice.iter().reduce(|a, b| if a <= b { a } else { b })
}

// === Approach 2: Multiple trait bounds ===
fn print_max<T: PartialOrd + Display>(slice: &[T]) -> Option<String> {
    find_max(slice).map(|v| format!("Max: {}", v))
}

fn clamp<T: PartialOrd>(value: T, lo: T, hi: T) -> T {
    if value < lo {
        lo
    } else if value > hi {
        hi
    } else {
        value
    }
}

// === Approach 3: Custom trait with bounds ===
trait Summarize: Display {
    fn summary(&self) -> String;
}

struct Stats {
    name: String,
    value: f64,
}

impl Display for Stats {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}={:.2}", self.name, self.value)
    }
}

impl Summarize for Stats {
    fn summary(&self) -> String {
        format!("[{}]", self)
    }
}

fn print_summaries<T: Summarize>(items: &[T]) -> String {
    items.iter().map(|i| i.summary()).collect::<Vec<_>>().join(", ")
}

// Generic pair operations with bounds
fn pair_map<T, U, F: Fn(T) -> U>(pair: (T, T), f: F) -> (U, U) {
    (f(pair.0), f(pair.1))
}

fn pair_fold<T, A, F: Fn(A, T) -> A>(init: A, pair: (T, T), f: F) -> A {
    let acc = f(init, pair.0);
    f(acc, pair.1)
}

fn main() {
    let nums = vec![3, 1, 4, 1, 5, 9, 2, 6];
    println!("Max: {:?}", find_max(&nums));
    println!("Min: {:?}", find_min(&nums));
    println!("{}", print_max(&nums).unwrap());

    println!("clamp(15, 0, 10) = {}", clamp(15, 0, 10));
    println!("clamp(-5, 0, 10) = {}", clamp(-5, 0, 10));

    let stats = vec![
        Stats { name: "temp".into(), value: 23.5 },
        Stats { name: "humidity".into(), value: 67.0 },
    ];
    println!("Summaries: {}", print_summaries(&stats));

    let doubled = pair_map((3, 4), |x| x * 2);
    println!("pair_map: {:?}", doubled);
}

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

    #[test]
    fn test_find_max() {
        assert_eq!(find_max(&[3, 1, 4, 1, 5, 9]), Some(&9));
        assert_eq!(find_max::<i32>(&[]), None);
        assert_eq!(find_max(&[42]), Some(&42));
    }

    #[test]
    fn test_find_min() {
        assert_eq!(find_min(&[3, 1, 4, 1, 5, 9]), Some(&1));
        assert_eq!(find_min::<i32>(&[]), None);
    }

    #[test]
    fn test_clamp() {
        assert_eq!(clamp(15, 0, 10), 10);
        assert_eq!(clamp(-5, 0, 10), 0);
        assert_eq!(clamp(5, 0, 10), 5);
        assert_eq!(clamp(1.5, 0.0, 1.0), 1.0);
    }

    #[test]
    fn test_print_max() {
        assert_eq!(print_max(&[1, 2, 3]), Some("Max: 3".to_string()));
        assert_eq!(print_max::<i32>(&[]), None);
    }

    #[test]
    fn test_pair_map() {
        assert_eq!(pair_map((3, 4), |x| x * 2), (6, 8));
        assert_eq!(pair_map((1.0, 2.0), |x: f64| x.sqrt()), (1.0, std::f64::consts::SQRT_2));
    }

    #[test]
    fn test_pair_fold() {
        assert_eq!(pair_fold(0, (3, 4), |acc, x| acc + x), 7);
    }

    #[test]
    fn test_summarize() {
        let s = Stats { name: "x".into(), value: 1.0 };
        assert_eq!(s.summary(), "[x=1.00]");
    }
}
(* Example 077: Generic Bounds *)
(* OCaml type constraints โ†’ Rust <T: Trait> bounds *)

(* Approach 1: Polymorphic functions with module constraints *)
module type Printable = sig
  type t
  val to_string : t -> string
end

module type Comparable = sig
  type t
  val compare : t -> t -> int
end

(* Approach 2: Using built-in polymorphic compare *)
let find_max lst =
  match lst with
  | [] -> None
  | [x] -> Some x
  | x :: rest -> Some (List.fold_left max x rest)

let find_min lst =
  match lst with
  | [] -> None
  | x :: rest -> Some (List.fold_left min x rest)

(* Approach 3: Explicit comparison function parameter *)
let find_max_by (cmp : 'a -> 'a -> int) = function
  | [] -> None
  | x :: rest ->
    Some (List.fold_left (fun acc y -> if cmp acc y >= 0 then acc else y) x rest)

let clamp ~lo ~hi x =
  if x < lo then lo
  else if x > hi then hi
  else x

(* Generic pair operations *)
let pair_map f (a, b) = (f a, f b)

let pair_fold f init (a, b) = f (f init a) b

(* Tests *)
let () =
  assert (find_max [3; 1; 4; 1; 5; 9] = Some 9);
  assert (find_max [] = None);
  assert (find_min [3; 1; 4; 1; 5; 9] = Some 1);

  assert (find_max_by compare [3; 1; 4; 1; 5; 9] = Some 9);
  assert (find_max_by (fun a b -> compare b a) [3; 1; 4; 1; 5; 9] = Some 1);

  assert (clamp ~lo:0 ~hi:10 15 = 10);
  assert (clamp ~lo:0 ~hi:10 (-5) = 0);
  assert (clamp ~lo:0 ~hi:10 5 = 5);

  assert (pair_map (fun x -> x * 2) (3, 4) = (6, 8));
  assert (pair_fold (+) 0 (3, 4) = 7);

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

๐Ÿ“Š Detailed Comparison

Comparison: Generic Bounds

Single Bound

OCaml โ€” Implicit polymorphism:

๐Ÿช Show OCaml equivalent
let find_max lst =
match lst with
| [] -> None
| x :: rest -> Some (List.fold_left max x rest)

Rust โ€” Explicit trait bound:

fn find_max<T: PartialOrd>(slice: &[T]) -> Option<&T> {
 slice.iter().reduce(|a, b| if a >= b { a } else { b })
}

Multiple Bounds

OCaml โ€” Structural (no syntax needed):

๐Ÿช Show OCaml equivalent
let print_max lst =
match find_max lst with
| None -> "empty"
| Some x -> Printf.sprintf "Max: %s" (string_of_int x)

Rust โ€” Combined with `+`:

fn print_max<T: PartialOrd + Display>(slice: &[T]) -> Option<String> {
 find_max(slice).map(|v| format!("Max: {}", v))
}

Trait Hierarchy

OCaml โ€” Module type inclusion:

๐Ÿช Show OCaml equivalent
module type Printable = sig
type t
val to_string : t -> string
end

Rust โ€” Supertrait:

trait Summarize: Display {
 fn summary(&self) -> String;
}