🦀 Functional Rust
🎬 How Rust Iterators Work Lazy evaluation, chaining, collect(), and zero-cost abstractions.
📝 Text version (for readers / accessibility)

• Iterators are lazy — .map(), .filter(), .take() build a chain but do no work until consumed

• .collect() triggers evaluation, transforming the chain into a Vec, HashMap, or other collection

• Zero-cost abstraction: iterator chains compile to the same machine code as hand-written loops

• .iter() borrows, .into_iter() consumes, .iter_mut() borrows mutably

• Chaining replaces nested loops with a readable, composable pipeline

098: Church Numerals — Functions as Numbers

Difficulty: 3 Level: Advanced Encode natural numbers as higher-order functions, then do arithmetic with those functions.

The Problem This Solves

Every number you've ever used in Rust is ultimately stored as bits: `0u8` is eight zero bits, `1u8` is `00000001`. That's the hardware's answer to "what is a number?" But there's a deeper answer that doesn't rely on hardware at all. In lambda calculus — the mathematical system that functional programming is built on — numbers don't exist as primitives. You have to build them from scratch using only functions. This forces you to answer: what is the essence of a number? The Church numeral answer is beautiful: the number N is a function that applies another function N times. Three is not the bit pattern `00000011`. Three is "whatever function you give me, I will call it three times." The integer 3 is just one way to observe that behaviour — you observe it by counting how many times "add 1" gets called starting from 0. This sounds academic but it matters: it's the mathematical proof that functions alone are Turing-complete. You don't need integers, booleans, or lists — you can build everything from functions. This example explores how far Rust lets you go down that road, and where it pushes back.

The Intuition

Forget integers for a moment. Imagine numbers as verbs, not nouns.
// You can think of it this way (ignoring Rust types for a moment):
let zero  = |f, x| x;
let one   = |f, x| f(x);
let two   = |f, x| f(f(x));
let three = |f, x| f(f(f(x)));

// Addition: m+n = apply f m times, then n more times
// two + three: apply f 5 times total
let five = |f, x| two(f, three(f, x));

// To read the number: f = "add 1", x = 0
// five(|x| x+1, 0) → 5
To add m + n: apply f m times starting from wherever n leaves off. That's it. To multiply m × n: applying n things m times = applying things m×n times.

How It Works in Rust

Three approaches are shown, from most faithful to most practical: Approach A — `Box<dyn Fn>` (true Church encoding)
// Type alias hides the complexity
type Church = Box<dyn Fn(Box<dyn Fn(i64) -> i64>) -> Box<dyn Fn(i64) -> i64>>;

fn zero() -> Church {
 Box::new(|_f| Box::new(|x| x))  // ignore f, return x unchanged
}

fn one() -> Church {
 Box::new(|f: Box<dyn Fn(i64) -> i64>| {
     Box::new(move |x| f(x))     // apply f once
 })
}

// Convert to integer: apply "add 1" to 0
fn to_int(n: &Church) -> i64 {
 let f = n(Box::new(|x| x + 1));
 f(0)
}
Each `Box` is a heap allocation. Compose many of these and you pay in heap allocations. OCaml does this on the stack; Rust can't because closure sizes are unknown at compile time. Approach B — struct wrapper (practical)
// Store the count, apply it when needed — same semantics, no Box cascade
#[derive(Clone, Copy)]
struct ChurchNum(usize);

impl ChurchNum {
 fn apply<T>(&self, f: impl Fn(T) -> T, x: T) -> T {
     (0..self.0).fold(x, |acc, _| f(acc))
     // fold applies f self.0 times — the Church numeral's actual behaviour
 }

 fn add(self, other: Self) -> Self { ChurchNum(self.0 + other.0) }
 fn mul(self, other: Self) -> Self { ChurchNum(self.0 * other.0) }
}
Approach C — generic function
// Direct Church application without wrapping
fn church_apply<T>(n: usize, f: impl Fn(T) -> T, x: T) -> T {
 (0..n).fold(x, |acc, _| f(acc))
}

// Church encoding of 3 applied to doubling starting from 1:
// 1 → 2 → 4 → 8
assert_eq!(church_apply(3, |x: i32| x * 2, 1), 8);

What This Unlocks

Key Differences

ConceptOCamlRust
Type of Church numeral`('a -> 'a) -> 'a -> 'a` (polymorphic, zero cost)`Box<dyn Fn(...)>` (heap, erased type)
PolymorphismImplicit, freeMust use generics or `dyn Fn`
Closure allocationStack/GCHeap via `Box`
Composition costFreeEach composition = heap alloc
Practical recommendationUse directlyUse struct wrapper
Successor function`let succ n f x = f (n f x)`10+ lines with `Rc` for sharing
//! # Church Numerals — Functions as Numbers
//!
//! Lambda calculus encoding where numbers are higher-order functions.
//! OCaml's polymorphic functions map to Rust's `Fn` trait objects or generics.

// ---------------------------------------------------------------------------
// Approach A: Using Box<dyn Fn> for Church numerals
// ---------------------------------------------------------------------------

/// A Church numeral: takes a function and a value, applies the function N times.
pub type Church = Box<dyn Fn(Box<dyn Fn(i64) -> i64>) -> Box<dyn Fn(i64) -> i64>>;

pub fn zero() -> Church {
    Box::new(|_f| Box::new(|x| x))
}

pub fn one() -> Church {
    Box::new(|f: Box<dyn Fn(i64) -> i64>| {
        Box::new(move |x| f(x))
    })
}

pub fn succ(n: &dyn Fn(Box<dyn Fn(i64) -> i64>) -> Box<dyn Fn(i64) -> i64>) -> Church {
    // Can't easily compose due to ownership... see Approach B
    let result = to_int_inner(n) + 1;
    from_int(result as usize)
}

fn to_int_inner(n: &dyn Fn(Box<dyn Fn(i64) -> i64>) -> Box<dyn Fn(i64) -> i64>) -> i64 {
    let f = n(Box::new(|x| x + 1));
    f(0)
}

pub fn to_int(n: &Church) -> i64 {
    let f = n(Box::new(|x| x + 1));
    f(0)
}

pub fn from_int(n: usize) -> Church {
    Box::new(move |f: Box<dyn Fn(i64) -> i64>| {
        Box::new(move |x| {
            let mut result = x;
            for _ in 0..n {
                result = f(result);
            }
            result
        })
    })
}

// ---------------------------------------------------------------------------
// Approach B: Simple integer-backed (practical encoding)
// ---------------------------------------------------------------------------

/// Practical Church numeral — store the count, apply when needed.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ChurchNum(pub usize);

impl ChurchNum {
    pub fn zero() -> Self { ChurchNum(0) }
    pub fn one() -> Self { ChurchNum(1) }
    pub fn succ(self) -> Self { ChurchNum(self.0 + 1) }
    pub fn add(self, other: Self) -> Self { ChurchNum(self.0 + other.0) }
    pub fn mul(self, other: Self) -> Self { ChurchNum(self.0 * other.0) }

    pub fn apply<T>(&self, f: impl Fn(T) -> T, x: T) -> T {
        let mut result = x;
        for _ in 0..self.0 {
            result = f(result);
        }
        result
    }

    pub fn to_int(&self) -> usize {
        self.apply(|x: usize| x + 1, 0)
    }
}

// ---------------------------------------------------------------------------
// Approach C: Generic function composition
// ---------------------------------------------------------------------------

pub fn church_apply<T>(n: usize, f: impl Fn(T) -> T, x: T) -> T {
    (0..n).fold(x, |acc, _| f(acc))
}

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

    #[test]
    fn test_zero() {
        assert_eq!(to_int(&zero()), 0);
    }

    #[test]
    fn test_one() {
        assert_eq!(to_int(&one()), 1);
    }

    #[test]
    fn test_from_int() {
        assert_eq!(to_int(&from_int(5)), 5);
    }

    #[test]
    fn test_church_num_basic() {
        let two = ChurchNum::one().succ();
        let three = ChurchNum::one().add(two);
        assert_eq!(three.to_int(), 3);
    }

    #[test]
    fn test_church_num_mul() {
        let two = ChurchNum(2);
        let three = ChurchNum(3);
        assert_eq!(two.mul(three).to_int(), 6);
    }

    #[test]
    fn test_church_apply() {
        assert_eq!(church_apply(3, |x: i32| x * 2, 1), 8); // 1 -> 2 -> 4 -> 8
    }

    #[test]
    fn test_church_apply_zero() {
        assert_eq!(church_apply(0, |x: i32| x + 1, 42), 42);
    }
}

fn main() {
    println!("{:?}", to_int(&zero()), 0);
    println!("{:?}", to_int(&one()), 1);
    println!("{:?}", to_int(&from_int(5)), 5);
}
let zero _f x = x
let one f x = f x
let succ n f x = f (n f x)
let add m n f x = m f (n f x)
let mul m n f = m (n f)
let to_int n = n (fun x -> x + 1) 0

let two = succ one
let three = add one two
let six = mul two three
let nine = mul three three

let () =
  Printf.printf "0=%d 1=%d 2=%d 3=%d 6=%d 9=%d\n"
    (to_int zero) (to_int one) (to_int two)
    (to_int three) (to_int six) (to_int nine)

📊 Detailed Comparison

Comparison: Church Numerals — OCaml vs Rust

Core Insight

Church numerals reveal a fundamental difference: OCaml's type system embraces higher-rank polymorphism naturally (`let zero f x = x` works for any `f`), while Rust's ownership model and monomorphized generics make pure Church encoding painful. Rust closures are concrete types, not abstract functions — each closure has a unique unnameable type, requiring `Box<dyn Fn>` for dynamic dispatch.

OCaml

🐪 Show OCaml equivalent
let zero _f x = x
let one f x = f x
let succ n f x = f (n f x)
let add m n f x = m f (n f x)
let mul m n f = m (n f)
let to_int n = n (fun x -> x + 1) 0

Rust — Practical encoding

#[derive(Clone, Copy)]
pub struct ChurchNum(pub usize);

impl ChurchNum {
 pub fn succ(self) -> Self { ChurchNum(self.0 + 1) }
 pub fn add(self, other: Self) -> Self { ChurchNum(self.0 + other.0) }

 pub fn apply<T>(&self, f: impl Fn(T) -> T, x: T) -> T {
     (0..self.0).fold(x, |acc, _| f(acc))
 }
}

Comparison Table

AspectOCamlRust
Zero`let zero _f x = x``Box::new(\_f\Box::new(\x\x))`
TypeInferred polymorphic`Box<dyn Fn(Box<dyn Fn>) -> Box<dyn Fn>>`
CompositionNatural function nestingOwnership tangles
Practical altNot needed`struct ChurchNum(usize)`
PerformanceGC-managed closuresHeap alloc per `Box<dyn Fn>`
Elegance⭐⭐⭐⭐⭐⭐⭐ (pure) / ⭐⭐⭐⭐ (struct)

Learner Notes

  • OCaml shines here: Lambda calculus is OCaml's home turf — the syntax is almost mathematical notation
  • Rust's closure problem: Each closure is a unique type. Composing them requires trait objects (`dyn Fn`) and heap allocation
  • `impl Fn` vs `dyn Fn`: `impl Fn` is zero-cost but monomorphic; `dyn Fn` is dynamic but allocates. Church numerals need the latter
  • Practical encoding wins: In real Rust code, wrap the value in a struct and provide `apply` — same Church semantics, zero ceremony
  • This is a language design lesson: Some abstractions are natural in some languages and awkward in others