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

078: Where Clauses

Difficulty: 2 Level: Intermediate Move complex generic constraints out of the function signature and into a `where` block for readability โ€” same semantics, cleaner code.

The Problem This Solves

Inline bounds (`fn foo<T: A + B, U: C, F: Fn(T) -> U>`) work fine for one or two constraints. But real-world generic functions can get messy fast. When you have four type parameters and each has two or three bounds, the function signature becomes a wall of angle brackets before you even see the function name. The `where` clause doesn't add power โ€” it's purely a formatting choice. But readability is correctness in practice: code that's hard to read gets misunderstood and misused. `where` lets you write the function name and parameters first, then declare the constraints separately, like a mathematical "where" statement. It also handles cases that inline syntax can't express cleanly: bounds on associated types (`T::Item: Display`), lifetime + trait combinations, and constraints on generic parameters of generic parameters.

The Intuition

Compare:
// Inline โ€” hard to read
fn transform<T, U, A, F: Fn(&T) -> U, G: Fn(A, U) -> A>(items: &[T], f: F, g: G, init: A) -> A

// where clause โ€” easier to scan
fn transform<T, U, A, F, G>(items: &[T], f: F, g: G, init: A) -> A
where
 F: Fn(&T) -> U,
 G: Fn(A, U) -> A,
In OCaml, functor signatures play a similar role โ€” listing what a module must provide, separately from how it's used. The `where` clause is Rust's way of saying "here's what I need from my type parameters."

How It Works in Rust

// Simple case: where clause for a multi-bound
fn sorted_summary<T>(items: &mut [T]) -> String
where
 T: Ord + Display,   // T must be sortable AND printable
{
 items.sort();
 items.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(", ")
}
// Complex: multiple type params, each with bounds
fn bounded_transform<T, F>(items: &[T], transform: F, lo: T, hi: T) -> Vec<T>
where
 T: PartialOrd + Clone,  // T needs comparison and cloning
 F: Fn(&T) -> T,         // F is a function from T to T
{
 items.iter().map(|x| {
     let y = transform(x);
     if y < lo { lo.clone() } else if y > hi { hi.clone() } else { y }
 }).collect()
}
// Arithmetic ops in where clause (using std::ops)
fn numeric_summary<T>(a: T, b: T) -> String
where
 T: Add<Output = T> + Mul<Output = T> + Display + Copy,
{
 format!("sum={}, product={}", a + b, a * b)
}
Use inline bounds for simple single-constraint cases. Switch to `where` when you have more than two type parameters or any parameter has more than two bounds.

What This Unlocks

Key Differences

ConceptOCamlRust
Constraint declaration locationInline in type signature or separate `sig`Inline `<T: Bound>` or `where T: Bound`
Bounds on associated typesModule type field constraints`where T::Item: Trait`
Readability choiceFunctor `sig` separates type from impl`where` clause separates params from constraints
Runtime impactNoneNone (both compile to same monomorphized code)
// Example 078: Where Clauses
// Complex where clauses vs inline bounds

use std::fmt::{Debug, Display};
use std::ops::{Add, Mul};

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

// === Approach 2: Where clauses (complex constraints) ===
fn transform_and_combine<T, U, A, F, G>(items: &[T], transform: F, combine: G, init: A) -> A
where
    F: Fn(&T) -> U,
    G: Fn(A, U) -> A,
{
    items.iter().fold(init, |acc, x| combine(acc, transform(x)))
}

fn filter_map_fold<T, U, A, P, F, G>(items: &[T], pred: P, transform: F, combine: G, init: A) -> A
where
    P: Fn(&T) -> bool,
    F: Fn(&T) -> U,
    G: Fn(A, U) -> A,
{
    items.iter().fold(init, |acc, x| {
        if pred(x) { combine(acc, transform(x)) } else { acc }
    })
}

// Where clause shines with multiple related bounds
fn sorted_summary<T>(items: &mut [T]) -> String
where
    T: Ord + Display,
{
    items.sort();
    items.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(", ")
}

// === Approach 3: Complex multi-type where clauses ===
fn bounded_transform<T, F>(items: &[T], transform: F, lo: T, hi: T) -> Vec<T>
where
    T: PartialOrd + Clone,
    F: Fn(&T) -> T,
{
    items.iter().map(|x| {
        let y = transform(x);
        if y < lo { lo.clone() }
        else if y > hi { hi.clone() }
        else { y }
    }).collect()
}

// Return type bounds in where clause
fn numeric_summary<T>(a: T, b: T) -> String
where
    T: Add<Output = T> + Mul<Output = T> + Display + Copy,
{
    let sum = a + b;
    let product = a * b;
    format!("sum={}, product={}", sum, product)
}

// Where clause with lifetime + trait bounds
fn longest_display<'a, T>(a: &'a T, b: &'a T) -> String
where
    T: Display + PartialOrd,
{
    if a >= b {
        format!("{}", a)
    } else {
        format!("{}", b)
    }
}

fn main() {
    let result = transform_and_combine(&[1, 2, 3, 4], |x| x * x, |a, b| a + b, 0);
    println!("Sum of squares: {}", result);

    let result2 = filter_map_fold(
        &[1, 2, 3, 4, 5, 6],
        |x| x % 2 == 0,
        |x| x * x,
        |a, b| a + b,
        0,
    );
    println!("Sum of even squares: {}", result2);

    let mut nums = vec![3, 1, 4, 1, 5];
    println!("Sorted: {}", sorted_summary(&mut nums));

    let bounded = bounded_transform(&[1, 2, 3, 4, 5], |x| x * 3, 0, 10);
    println!("Bounded: {:?}", bounded);

    println!("{}", numeric_summary(3, 4));
    println!("Longest: {}", longest_display(&10, &20));
}

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

    #[test]
    fn test_transform_and_combine() {
        let r = transform_and_combine(&[1, 2, 3, 4], |x| x * x, |a, b| a + b, 0);
        assert_eq!(r, 30);
    }

    #[test]
    fn test_filter_map_fold() {
        let r = filter_map_fold(&[1, 2, 3, 4, 5, 6], |x| x % 2 == 0, |x| x * x, |a, b| a + b, 0);
        assert_eq!(r, 56); // 4 + 16 + 36
    }

    #[test]
    fn test_sorted_summary() {
        let mut v = vec![3, 1, 4, 1, 5];
        assert_eq!(sorted_summary(&mut v), "1, 1, 3, 4, 5");
    }

    #[test]
    fn test_bounded_transform() {
        let r = bounded_transform(&[1, 2, 3, 4, 5], |x| x * 3, 0, 10);
        assert_eq!(r, vec![3, 6, 9, 10, 10]);
    }

    #[test]
    fn test_numeric_summary() {
        assert_eq!(numeric_summary(3, 4), "sum=7, product=12");
    }

    #[test]
    fn test_longest_display() {
        assert_eq!(longest_display(&10, &20), "20");
        assert_eq!(longest_display(&"zebra", &"apple"), "zebra");
    }

    #[test]
    fn test_empty_slice() {
        let r = transform_and_combine::<i32, i32, i32, _, _>(&[], |x| x * x, |a, b| a + b, 0);
        assert_eq!(r, 0);
    }
}
(* Example 078: Where Clauses *)
(* OCaml doesn't have where clauses โ€” constraints are structural *)

(* Approach 1: Module signature constraints *)
module type Mappable = sig
  type 'a t
  val map : ('a -> 'b) -> 'a t -> 'b t
  val to_list : 'a t -> 'a list
end

module type Foldable = sig
  type 'a t
  val fold : ('b -> 'a -> 'b) -> 'b -> 'a t -> 'b
end

(* Approach 2: Functor with multiple constraints *)
module MakeProcessor (M : Mappable) = struct
  let double_all xs = M.map (fun x -> x * 2) xs
  let stringify xs = M.map string_of_int xs
end

(* Approach 3: Plain polymorphic functions *)
let transform_and_combine ~transform ~combine ~init items =
  List.fold_left (fun acc x -> combine acc (transform x)) init items

let filter_map_fold ~pred ~transform ~combine ~init items =
  List.fold_left
    (fun acc x -> if pred x then combine acc (transform x) else acc)
    init items

(* Complex constraint: needs both compare and string conversion *)
let sorted_summary items to_str =
  let sorted = List.sort compare items in
  String.concat ", " (List.map to_str sorted)

let bounded_transform ~lo ~hi ~transform items =
  List.map (fun x ->
    let y = transform x in
    if y < lo then lo
    else if y > hi then hi
    else y
  ) items

(* Tests *)
let () =
  let result = transform_and_combine
    ~transform:(fun x -> x * x)
    ~combine:(+) ~init:0 [1; 2; 3; 4] in
  assert (result = 30);

  let result2 = filter_map_fold
    ~pred:(fun x -> x mod 2 = 0)
    ~transform:(fun x -> x * x)
    ~combine:(+) ~init:0 [1; 2; 3; 4; 5; 6] in
  assert (result2 = 56);

  let summary = sorted_summary [3; 1; 4; 1; 5] string_of_int in
  assert (summary = "1, 1, 3, 4, 5");

  let bounded = bounded_transform ~lo:0 ~hi:10
    ~transform:(fun x -> x * 3) [1; 2; 3; 4; 5] in
  assert (bounded = [3; 6; 9; 10; 10]);

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

๐Ÿ“Š Detailed Comparison

Comparison: Where Clauses

Inline vs Where Clause

Rust โ€” Inline bounds (simple):

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

Rust โ€” Where clause (complex):

fn transform_and_combine<T, U, A, F, G>(items: &[T], transform: F, combine: G, init: A) -> A
where
 F: Fn(&T) -> U,
 G: Fn(A, U) -> A,
{
 items.iter().fold(init, |acc, x| combine(acc, transform(x)))
}

OCaml Equivalent โ€” No Explicit Constraints

OCaml:

๐Ÿช Show OCaml equivalent
let transform_and_combine ~transform ~combine ~init items =
List.fold_left (fun acc x -> combine acc (transform x)) init items
(* Types are fully inferred, no constraints written *)

Rust:

fn transform_and_combine<T, U, A, F, G>(items: &[T], transform: F, combine: G, init: A) -> A
where F: Fn(&T) -> U, G: Fn(A, U) -> A,
{ /* ... */ }

Multiple Related Bounds

OCaml:

๐Ÿช Show OCaml equivalent
let sorted_summary items to_str =
let sorted = List.sort compare items in
String.concat ", " (List.map to_str sorted)

Rust:

fn sorted_summary<T>(items: &mut [T]) -> String
where
 T: Ord + Display,
{
 items.sort();
 items.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(", ")
}