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

390: Type Alias impl Trait (TAIT)

Difficulty: 4 Level: Expert Name an opaque type so you can refer to it in multiple places โ€” the named version of `impl Trait`.

The Problem This Solves

`impl Trait` in return position lets you hide a concrete type: `fn make_iter() -> impl Iterator<Item = i32>`. That's great for one function. But what if two functions return the same opaque type and you want to store them in a struct, pass them to a third function, or use the type in a trait definition? Anonymous `impl Trait` can't be named โ€” every occurrence is a fresh, distinct opaque type. Type Alias impl Trait (TAIT) solves this: `type MyIter = impl Iterator<Item = i32>`. Now `MyIter` is a stable name for one concrete type that satisfies `Iterator<Item = i32>`. Multiple functions can return `MyIter`, structs can store `MyIter` fields, and the compiler infers the concrete type from the definitions โ€” you never write it explicitly. TAIT also unlocks self-referential and recursive type definitions that were impossible with anonymous `impl Trait`. The feature stabilized in Rust 1.75 as part of RPITIT (return-position impl trait in traits), making it available in stable Rust.

The Intuition

Think of TAIT as giving a nickname to "whatever concrete type the compiler figures out here." The compiler still knows the exact type โ€” it's just you, the programmer, who doesn't write it. The alias is opaque to callers (they see the trait bound) but transparent to the compiler (it resolves to the monomorphic type). The key distinction from `Box<dyn Iterator>`: TAIT is still zero-cost. No heap allocation, no dynamic dispatch. The concrete type is fully known at compile time โ€” it's just hidden behind an alias.

How It Works in Rust

// Without TAIT: each function returns a distinct anonymous type
fn counter() -> impl Iterator<Item = i32> { 0..10 }
fn evens() -> impl Iterator<Item = i32> { (0..20).filter(|x| x % 2 == 0) }
// Can't unify these โ€” different opaque types

// With TAIT (Rust 1.75+):
type Counts = impl Iterator<Item = i32>;

fn counter() -> Counts { 0..10 }
// Another function can also return Counts โ€” same concrete type

// Stable approximation with Box<dyn>:
type BoxedIter<T> = Box<dyn Iterator<Item = T>>;

fn range_iter(n: i32) -> BoxedIter<i32> { Box::new(0..n) }
fn evens_iter(n: i32) -> BoxedIter<i32> { Box::new((0..n).filter(|x| x % 2 == 0)) }
// Now both share the BoxedIter<i32> type โ€” storable, passable, consistent

What This Unlocks

Key Differences

ConceptOCamlRust
Opaque type`module type S = sig type t end` with ascription`type Alias = impl Trait`
Multiple usesModule sharing / functor argumentType alias reused across functions
Concrete typeKnown inside module, hidden outsideInferred by compiler, hidden from programmer
Dynamic dispatchN/A (no vtable by default)`Box<dyn Trait>` (heap, dynamic)
Zero-cost opaqueModule ascription (stack, monomorphic)TAIT (stack, monomorphic)
// Type Alias Impl Trait (TAIT) in Rust
// Note: TAIT requires nightly or Rust 1.75+ for full support.
// Here we show the stable approximation and the concept.

// Stable: returning impl Trait from functions
fn make_counter(start: i32, end: i32) -> impl Iterator<Item = i32> {
    start..end
}

fn make_even_filter(v: Vec<i32>) -> impl Iterator<Item = i32> {
    v.into_iter().filter(|x| x % 2 == 0)
}

// With a named type alias (stable, but requires boxing or concrete type)
type BoxedIter<T> = Box<dyn Iterator<Item = T>>;

fn range_boxed(start: i32, end: i32) -> BoxedIter<i32> {
    Box::new(start..end)
}

fn evens_boxed(v: Vec<i32>) -> BoxedIter<i32> {
    Box::new(v.into_iter().filter(|x| x % 2 == 0))
}

// TAIT concept: type Squares = impl Iterator<Item=i64>
// (requires nightly; shown conceptually)
fn squares(n: u32) -> impl Iterator<Item = i64> {
    (1..=n).map(|x| (x as i64) * (x as i64))
}

fn main() {
    let counter = make_counter(1, 6);
    println!("Counter: {:?}", counter.collect::<Vec<_>>());

    let evens = make_even_filter(vec![1, 2, 3, 4, 5, 6]);
    println!("Evens: {:?}", evens.collect::<Vec<_>>());

    let boxed: BoxedIter<i32> = range_boxed(10, 15);
    println!("Boxed range: {:?}", boxed.collect::<Vec<_>>());

    println!("Squares: {:?}", squares(5).collect::<Vec<_>>());

    // Chain iterators using the same opaque type concept
    let chained: Vec<i32> = make_counter(1, 4)
        .chain(make_counter(10, 13))
        .collect();
    println!("Chained: {:?}", chained);
}

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

    #[test]
    fn test_counter() {
        let v: Vec<i32> = make_counter(0, 5).collect();
        assert_eq!(v, vec![0, 1, 2, 3, 4]);
    }

    #[test]
    fn test_even_filter() {
        let v: Vec<i32> = make_even_filter(vec![1,2,3,4,5,6]).collect();
        assert_eq!(v, vec![2, 4, 6]);
    }

    #[test]
    fn test_squares() {
        let v: Vec<i64> = squares(4).collect();
        assert_eq!(v, vec![1, 4, 9, 16]);
    }
}
(* Type alias impl Trait concept in OCaml *)

(* In OCaml, we achieve similar opaqueness via module types *)
module type IntIter = sig
  type t
  val create : int -> int -> t
  val next : t -> (int * t) option
  val to_list : t -> int list
end

module RangeIter : IntIter = struct
  type t = { current: int; stop: int }
  let create start stop = { current = start; stop }
  let next { current; stop } =
    if current >= stop then None
    else Some (current, { current = current + 1; stop })
  let rec to_list state =
    match next state with
    | None -> []
    | Some (x, rest) -> x :: to_list rest
end

let () =
  let it = RangeIter.create 1 6 in
  let values = RangeIter.to_list it in
  Printf.printf "Range 1..6: [%s]\n"
    (String.concat "; " (List.map string_of_int values));
  let evens = List.filter (fun x -> x mod 2 = 0) values in
  Printf.printf "Evens: [%s]\n"
    (String.concat "; " (List.map string_of_int evens))