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

391: impl Trait in Return Position

Difficulty: 2 Level: Intermediate Return `impl Iterator` (or any trait) to hide the concrete type โ€” zero-cost abstraction without naming the type.

The Problem This Solves

Before `impl Trait` in return position, hiding a concrete return type required either exposing it explicitly (tying your API to implementation details) or boxing it (`Box<dyn Iterator>` โ€” heap allocation, dynamic dispatch, runtime cost). Neither was satisfying for iterator-heavy code. The problem is common: you chain several iterator adapters inside a function and the concrete type becomes something like `Filter<Map<std::ops::Range<i32>, fn(i32) -> i32>, fn(&i32) -> bool>`. This is an implementation detail. Callers don't need to know it, and you don't want to commit to it โ€” it changes every time you refactor the internals. `impl Trait` in return position (RPIT) says: "this function returns some concrete type that implements this trait." The type is fixed and known at compile time โ€” the compiler sees through the abstraction โ€” but callers only see the trait bound. No allocation, no dynamic dispatch, no exposed internals.

The Intuition

`impl Trait` is a promise: "I'll give you something that can do X โ€” you don't need to know what it is, just that it can." The concrete type is locked in at compile time (monomorphic), which means the optimizer sees everything. You get the ergonomics of abstraction with the performance of concrete types. The constraint: every code path in the function must return the same concrete type. If you need to return different types conditionally (odd path vs. even path), you need `Box<dyn Trait>` or an enum.

How It Works in Rust

// Return impl Iterator โ€” concrete type is hidden from callers
fn make_range(start: i32, end: i32) -> impl Iterator<Item = i32> {
 start..end  // concrete type: Range<i32>
}

// Return impl Fn โ€” hide closure type
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
 move |x| x + n  // concrete type: [closure@...]
}

// Chain adapters โ€” concrete type gets complex, impl hides it
fn evens_doubled(start: i32, end: i32) -> impl Iterator<Item = i32> {
 (start..end).filter(|x| x % 2 == 0).map(|x| x * 2)
}

// Can't return different types from branches โ€” use Box<dyn> for that
fn either_iter(flag: bool) -> Box<dyn Iterator<Item = i32>> {
 if flag { Box::new(0..5) } else { Box::new(vec![10, 20, 30].into_iter()) }
}

// Use it just like any iterator
let sum: i32 = evens_doubled(1, 11).sum(); // โ†’ 60

What This Unlocks

Key Differences

ConceptOCamlRust
Opaque return typeModule type ascription`fn f() -> impl Trait`
Concrete type hidden from callerYes (module signature)Yes (compiler knows, caller doesn't)
Runtime costZero (monomorphic modules)Zero (monomorphized)
Conditional different typesVariant / functorRequires `Box<dyn Trait>` or enum
Closure returnFirst-class values (no boxing)`impl Fn(...)` (no boxing)
// impl Trait in return position in Rust

// Return opaque iterator (concrete type: Map<Range<i32>, fn>)
fn make_range(start: i32, end: i32) -> impl Iterator<Item = i32> {
    start..end
}

// Return opaque closure
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n
}

fn make_multiplier(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x * n
}

// Chain returns
fn evens_in_range(start: i32, end: i32) -> impl Iterator<Item = i32> {
    (start..end).filter(|x| x % 2 == 0)
}

fn apply_twice<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
    f(f(x))
}

// Return impl Trait from different branches requires boxing (or enum)
fn make_iterator(use_odds: bool) -> Box<dyn Iterator<Item = i32>> {
    if use_odds {
        Box::new((1..10).filter(|x| x % 2 != 0))
    } else {
        Box::new((1..10).filter(|x| x % 2 == 0))
    }
}

fn main() {
    let v: Vec<i32> = make_range(1, 6).collect();
    println!("Range: {:?}", v);

    let add5 = make_adder(5);
    println!("add5(10) = {}", add5(10));

    let times3 = make_multiplier(3);
    println!("apply_twice times3 2 = {}", apply_twice(times3, 2));

    let evens: Vec<i32> = evens_in_range(1, 11).collect();
    println!("Evens in 1..11: {:?}", evens);

    let odds: Vec<i32> = make_iterator(true).collect();
    println!("Odds: {:?}", odds);
}

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

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

    #[test]
    fn test_make_adder() {
        let f = make_adder(7);
        assert_eq!(f(3), 10);
    }

    #[test]
    fn test_apply_twice() {
        let f = make_multiplier(2);
        assert_eq!(apply_twice(f, 3), 12); // 3*2=6, 6*2=12
    }
}
(* impl Trait return in OCaml - closures and module ascription *)

(* Return a "hidden" iterator implementation *)
let range_iter start stop =
  (* Returns a sequence function โ€” opaque to caller *)
  let rec gen n () =
    if n >= stop then Seq.Nil
    else Seq.Cons (n, gen (n + 1))
  in
  gen start

(* Return a function (closure) with hidden implementation *)
let make_adder n =
  fun x -> x + n  (* concrete type hidden, just Int -> Int *)

let make_multiplier n =
  fun x -> x * n

let apply_twice f x = f (f x)

let () =
  let seq = range_iter 1 6 in
  let items = List.of_seq seq in
  Printf.printf "Range: [%s]\n"
    (String.concat "; " (List.map string_of_int items));
  let add5 = make_adder 5 in
  let times3 = make_multiplier 3 in
  Printf.printf "add5(10) = %d\n" (add5 10);
  Printf.printf "apply_twice times3 2 = %d\n" (apply_twice times3 2)