๐Ÿฆ€ Functional Rust

603: Functor Laws in Practice

Difficulty: 5 Level: Master Implement the Functor trait for `Option`, `Result`, and `Vec`, then verify both Functor laws hold โ€” the same laws that make `map` trustworthy across Rust's entire standard library.

The Problem This Solves

You've been using `.map()` on `Option`, `Result`, and `Vec` for years. But have you ever wondered: why can you chain them safely? Why does replacing two `.map()` calls with one always work? Why does the compiler never warn you that `.map(|x| x)` is a no-op? The answer is that these types all follow two precise rules โ€” the Functor laws. Without these rules, "map" would just be a method name with unpredictable behavior. With them, `map` has a contract you can rely on.
// You probably write this all the time:
let result = some_option
 .map(parse_string)
 .map(validate_value)
 .map(format_output);

// You trust this is equivalent to:
let result = some_option.map(|s| format_output(validate_value(parse_string(s))));
That trust is the Functor composition law. This example cracks open `Option`, `Result`, and `Vec` at the trait level โ€” you implement the `Functor` trait yourself, verify both laws, and see exactly why the standard library's `map` is reliable. This concept exists to solve exactly that pain: understanding why map-heavy Rust code is safe to refactor.

The Intuition

Think of a Functor like a labeled shipping container. The label says what's inside (the type). You can change what's inside without relabeling โ€” that's `map`. Two rules must hold: Law 1 โ€” Identity: Relabeling with "keep everything the same" should produce an identical container. `container.map(|x| x)` must equal `container`. If a map changes anything when you give it an identity function, the container has hidden state. That's a bug. Law 2 โ€” Composition: Two relabeling trips should produce the same result as one combined trip. `.map(f).map(g)` must equal `.map(|x| g(f(x)))`. If they differ, your container is accumulating state between maps โ€” again, hidden behavior. These laws together mean: map can only transform values, never the container's structure, count, or metadata. No side effects. No surprises. In Rust's standard library, these laws are upheld by design for `Option`, `Vec`, and `Result`. This example shows you how to verify that yourself โ€” and how to apply the same discipline to your own types.

How It Works in Rust

Step 1 โ€” Define the Functor trait using GATs:
trait Functor {
 type Inner;          // the A in Container<A>
 type Mapped<B>;      // the Container<B> you get after mapping
 fn fmap<B>(self, f: impl FnMut(Self::Inner) -> B) -> Self::Mapped<B>;
}
GATs (Generic Associated Types) are what make `type Mapped<B>` possible โ€” they let the trait express "a version of myself holding type B instead of A." Step 2 โ€” Implement for the three standard types:
impl<A> Functor for Option<A> {
 type Inner = A;
 type Mapped<B> = Option<B>;
 fn fmap<B>(self, f: impl FnMut(A) -> B) -> Option<B> {
     self.map(f)  // delegate to the built-in map
 }
}

impl<A, E> Functor for Result<A, E> {
 type Inner = A;
 type Mapped<B> = Result<B, E>;
 fn fmap<B>(self, f: impl FnMut(A) -> B) -> Result<B, E> {
     self.map(f)  // only transforms the Ok side
 }
}

impl<A> Functor for Vec<A> {
 type Inner = A;
 type Mapped<B> = Vec<B>;
 fn fmap<B>(self, f: impl FnMut(A) -> B) -> Vec<B> {
     self.into_iter().map(f).collect()
 }
}
Step 3 โ€” Verify Law 1 (Identity):
fn check_identity_option<A: Clone + PartialEq>(x: Option<A>) -> bool {
 x.clone().fmap(|v| v) == x  // map with identity must return original
}

assert!(check_identity_option(Some(42)));   // true
assert!(check_identity_option::<i32>(None)); // true
Step 4 โ€” Verify Law 2 (Composition):
fn check_composition_option<A, B, C>(
 x: Option<A>,
 f: impl Fn(A) -> B + Clone,
 g: impl Fn(B) -> C,
) -> bool
where A: Clone, B: Clone + PartialEq, C: PartialEq,
{
 let composed   = x.clone().fmap(|a| g(f(a)));  // one fmap with gโˆ˜f
 let sequential = x.fmap(f).fmap(g);             // two fmaps in sequence
 composed == sequential
}

let f = |x: i32| x * 2;
let g = |x: i32| x + 1;
assert!(check_composition_option(Some(5), f, g));  // true

// Also verify for Vec:
let xs = vec![1, 2, 3, 4, 5];
let composed:   Vec<i32> = xs.clone().fmap(|x| g(f(x)));
let sequential: Vec<i32> = xs.clone().fmap(f).fmap(g);
assert_eq!(composed, sequential);  // Law 2 holds for Vec
Step 5 โ€” What the laws prevent: If someone wrote a "Functor" that counted map calls:
// This would break Law 1:
struct CountingBox<T> { value: T, count: usize }
// After .fmap(|x| x): count would be 1, but original has count 0 โ†’ not equal
// Identity law violated โ†’ NOT a valid Functor

What This Unlocks

Real codebases where Functor laws matter in practice: `tokio`'s `Future::map` (async transformations must compose), `rayon`'s parallel iterators (parallel map must equal sequential map for correctness), `serde`'s value transformations, and any generic library that accepts user-provided `map`-able types.

Key Differences

ConceptOCamlRust
Functor interface`module type FUNCTOR` with `type 'a t` and `val map``trait Functor` with GATs (`type Inner`, `type Mapped<B>`)
Higher-kinded typesNative โ€” `'a option` is a type constructorSimulated with GATs โ€” `type Mapped<B>` approximates `F<B>`
Identity function`Fun.id` from stdlibWritten as `\v\v` inline (no stdlib `id`)
Composition`let (%) f g x = f (g x)` โ€” explicit compose operatorClosure: `\a\g(f(a))` inline
Law verification`assert (map Fun.id xs = xs)``assert_eq!(xs.clone().fmap(\v\v), xs)` with `Clone + PartialEq`
Cloning for testsNot needed โ€” immutable by defaultRequired: need original and mapped both alive for comparison
`Vec` equivalent`'a list` with `List.map``Vec<A>` with `into_iter().map(f).collect()`
`Result` functor`Result.map` (maps over `Ok` variant)`Result::map` (same โ€” only transforms `Ok`)
Compile-time law checksNot possible โ€” convention onlyNot possible โ€” tested at runtime with `#[test]`
trait Functor {
    type Inner;
    type Mapped<B>;
    fn fmap<B>(self, f: impl FnMut(Self::Inner) -> B) -> Self::Mapped<B>;
}

impl<A> Functor for Option<A> {
    type Inner = A;
    type Mapped<B> = Option<B>;
    fn fmap<B>(self, f: impl FnMut(A) -> B) -> Option<B> { self.map(f) }
}

impl<A,E> Functor for Result<A,E> {
    type Inner = A;
    type Mapped<B> = Result<B,E>;
    fn fmap<B>(self, f: impl FnMut(A) -> B) -> Result<B,E> { self.map(f) }
}

impl<A> Functor for Vec<A> {
    type Inner = A;
    type Mapped<B> = Vec<B>;
    fn fmap<B>(self, f: impl FnMut(A) -> B) -> Vec<B> { self.into_iter().map(f).collect() }
}

// Law checking
fn check_identity_option<A: Clone + PartialEq>(x: Option<A>) -> bool {
    x.clone().fmap(|v| v) == x
}

fn check_composition_option<A,B,C>(
    x: Option<A>,
    f: impl Fn(A) -> B + Clone,
    g: impl Fn(B) -> C,
) -> bool
where A: Clone, B: Clone + PartialEq, C: PartialEq,
{
    let gf = {let f2 = f.clone(); move |a| g(f2(a))};
    let compose = x.clone().fmap(gf);
    let sequential = x.fmap(f).fmap(g);
    compose == sequential
}

fn main() {
    // Identity law: map(id) = id
    let x: Option<i32> = Some(42);
    println!("Option identity law: {}", check_identity_option(x));

    let xs = vec![1,2,3,4,5];
    let id_check: Vec<i32> = xs.clone().fmap(|x|x);
    println!("Vec identity law: {}", id_check == xs);

    // Composition law: map(gโˆ˜f) == map(g)โˆ˜map(f)
    let f = |x: i32| x * 2;
    let g = |x: i32| x + 1;
    println!("Option composition law: {}", check_composition_option(Some(5), f, g));

    let comp: Vec<_> = xs.clone().fmap(|x| g(f(x)));
    let seq:  Vec<_> = xs.clone().fmap(f).fmap(g);
    println!("Vec composition law: {}", comp == seq);

    // Demonstrating fmap on Result
    let r: Result<i32, &str> = Ok(5);
    println!("Result fmap: {:?}", r.fmap(|x| x * 10));
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test] fn identity_some()  { assert!(check_identity_option(Some(42))); }
    #[test] fn identity_none()  { assert!(check_identity_option::<i32>(None)); }
    #[test] fn composition()    { assert!(check_composition_option(Some(5), |x:i32|x*2, |x|x+1)); }
}
(* Functor laws verification in OCaml *)
module type FUNCTOR = sig
  type 'a t
  val map : ('a -> 'b) -> 'a t -> 'b t
end

module OptionFunctor : FUNCTOR with type 'a t = 'a option = struct
  type 'a t = 'a option
  let map = Option.map
end

module ListFunctor : FUNCTOR with type 'a t = 'a list = struct
  type 'a t = 'a list
  let map = List.map
end

(* Law verification *)
let identity_law fa =
  List.map Fun.id fa = fa

let composition_law f g fa =
  List.map (fun x -> g (f x)) fa = List.map g (List.map f fa)

let () =
  let xs = [1;2;3;4;5] in
  Printf.printf "identity law: %b\n" (identity_law xs);
  let f x = x * 2 in
  let g x = x + 1 in
  Printf.printf "composition law: %b\n" (composition_law f g xs)