057: Fold Left
Difficulty: 2 Level: Beginner Accumulate a result left-to-right with a running accumulator โ the tail-recursive fold.The Problem This Solves
Sum a list of numbers. Find the maximum. Reverse a list. Build a string from parts. All of these are left folds: start with an initial value, walk the list left to right, update the accumulator at each step, return the final accumulated result. `fold_left` is the workhorse of functional programming. It's the pattern behind `sum`, `product`, `max`, and `reverse`. Once you see `fold_left`, you see it everywhere. And unlike `fold_right`, it's tail-recursive โ safe for arbitrarily large inputs because it doesn't build up a stack. Rust's `Iterator::fold` is `fold_left`. Every time you call `.fold(init, |acc, x| ...)`, you're writing a left fold.The Intuition
`fold_left f init [a, b, c]` evaluates as:((init f a) f b) f c
Left to right. The accumulator starts at `init`, is updated by `f(acc, a)`, then `f(result, b)`, then `f(result, c)`. Each step consumes the current accumulator and produces the next one.
For sum: `((0 + 3) + 1) + 4 = 8`. The accumulator carries the running total. For reverse: start with `[]`, prepend each element: `[] โ [a] โ [b,a] โ [c,b,a]`.
How It Works in Rust
// Generic fold_left โ iterative (Rust has no TCO guarantee)
pub fn fold_left<T, A>(f: impl Fn(A, &T) -> A, mut acc: A, xs: &[T]) -> A {
for x in xs {
acc = f(acc, x); // tail-recursive in OCaml; iterative loop in Rust
}
acc
}
// Sum, product, max via fold_left:
fold_left(|acc, &x| acc + x, 0, &[1, 2, 3]) // โ 6
fold_left(|acc, &x| acc * x, 1, &[1, 2, 3]) // โ 6
// Max with Option for empty-safety:
let (&first, rest) = xs.split_first()?;
fold_left(|acc, &x| acc.max(x), first, rest) // โ Some(max)
// Reverse via fold_left โ the classic FP trick:
fold_left(|mut acc: Vec<i64>, &x| { acc.insert(0, x); acc }, vec![], xs)
// Rust's built-in left fold:
xs.iter().copied().fold(0i64, i64::wrapping_add)
Note: OCaml guarantees tail-call optimisation (TCO) for `fold_left`. Rust does not. Our implementation uses a `for` loop, which has the same constant-stack behaviour without relying on TCO.
What This Unlocks
- Universal aggregation โ any "combine a list into a single value" operation is a fold: sum, product, max, count, string join.
- Building data structures โ use fold to construct maps, sets, histograms from a sequence.
- Understanding `Iterator::fold` โ Rust's standard fold is right there in the iterator chain; this example makes its mechanics explicit.
Key Differences
| Concept | OCaml | Rust | ||
|---|---|---|---|---|
| Tail recursion | Guaranteed TCO โ safe for millions of elements | No TCO โ use iterative loop instead | ||
| Empty-list max | `List.hd` panics on empty | Return `Option<T>` โ explicit emptiness | ||
| Accumulator | Immutable, returned each step | `mut acc` updated in place each iteration | ||
| In-place reverse | Allocates new list | `Vec::reverse()` โ O(1) extra space | ||
| Standard library | `List.fold_left f init lst` | `iter().fold(init, \ | acc, x\ | ...)` |
//! # fold_left โ Tail-Recursive Accumulator
//!
//! OCaml's `fold_left` processes a list left to right with an accumulator:
//! fold_left f init [a; b; c] = f (f (f init a) b) c
//!
//! This is tail-recursive in OCaml (and maps directly to Rust's `Iterator::fold`).
// ---------------------------------------------------------------------------
// Approach A: Idiomatic Rust โ iterator methods
// ---------------------------------------------------------------------------
pub fn sum_idiomatic(xs: &[i64]) -> i64 {
xs.iter().sum()
}
pub fn product_idiomatic(xs: &[i64]) -> i64 {
xs.iter().product()
}
/// Maximum element. Returns `None` for empty slices.
/// Uses `iter().copied().max()` which returns `Option<i64>`.
pub fn maximum_idiomatic(xs: &[i64]) -> Option<i64> {
// Unlike OCaml's version which panics on empty list, we return Option
xs.iter().copied().max()
}
pub fn reverse_idiomatic(xs: &[i64]) -> Vec<i64> {
let mut v = xs.to_vec();
v.reverse();
v
}
// ---------------------------------------------------------------------------
// Approach B: Functional / explicit fold_left (recursive, tail-recursive style)
// ---------------------------------------------------------------------------
/// A generic left fold mirroring OCaml's `fold_left`.
///
/// Rust doesn't guarantee TCO, but the structure is identical to OCaml's
/// tail-recursive fold_left. For large inputs, the iterative version is safer.
pub fn fold_left<T, A>(f: impl Fn(A, &T) -> A, mut acc: A, xs: &[T]) -> A {
for x in xs {
acc = f(acc, x);
}
acc
}
pub fn sum_functional(xs: &[i64]) -> i64 {
fold_left(|acc, &x| acc + x, 0, xs)
}
pub fn product_functional(xs: &[i64]) -> i64 {
fold_left(|acc, &x| acc * x, 1, xs)
}
pub fn maximum_functional(xs: &[i64]) -> Option<i64> {
// Safe version: use first element as initial accumulator
let (&first, rest) = xs.split_first()?;
Some(fold_left(
|acc, &x| if x > acc { x } else { acc },
first,
rest,
))
}
pub fn reverse_functional(xs: &[i64]) -> Vec<i64> {
// Mirrors OCaml: fold_left (fun acc x -> x :: acc) [] lst
fold_left(
|mut acc: Vec<i64>, &x| {
acc.push(x); // push + final reverse, or insert(0, x) like OCaml's cons
acc
},
Vec::new(),
xs,
)
.into_iter()
.rev()
.collect()
}
// ---------------------------------------------------------------------------
// Approach C: Iterator::fold โ Rust's built-in left fold
// ---------------------------------------------------------------------------
/// Sum using Iterator::fold โ explicitly showing the fold call.
/// We add a type annotation to show fold's signature clearly.
pub fn sum_iter_fold(xs: &[i64]) -> i64 {
xs.iter().copied().fold(0i64, i64::wrapping_add)
}
/// Product using Iterator::fold.
pub fn product_iter_fold(xs: &[i64]) -> i64 {
xs.iter().copied().fold(1i64, i64::wrapping_mul)
}
pub fn reverse_iter_fold(xs: &[i64]) -> Vec<i64> {
// fold left, prepending each element
xs.iter().fold(Vec::new(), |mut acc, &x| {
acc.insert(0, x);
acc
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sum_basic() {
let xs = [3, 1, 4, 1, 5, 9, 2, 6];
assert_eq!(sum_idiomatic(&xs), 31);
assert_eq!(sum_functional(&xs), 31);
assert_eq!(sum_iter_fold(&xs), 31);
}
#[test]
fn test_sum_empty() {
let xs: &[i64] = &[];
assert_eq!(sum_idiomatic(xs), 0);
assert_eq!(sum_functional(xs), 0);
assert_eq!(sum_iter_fold(xs), 0);
}
#[test]
fn test_product_single() {
let xs = [7];
assert_eq!(product_idiomatic(&xs), 7);
assert_eq!(product_functional(&xs), 7);
assert_eq!(product_iter_fold(&xs), 7);
}
#[test]
fn test_product_basic() {
let xs = [3, 1, 4, 1, 5, 9, 2, 6];
assert_eq!(product_idiomatic(&xs), 6480);
assert_eq!(product_functional(&xs), 6480);
assert_eq!(product_iter_fold(&xs), 6480);
}
#[test]
fn test_maximum() {
let xs = [3, 1, 4, 1, 5, 9, 2, 6];
assert_eq!(maximum_idiomatic(&xs), Some(9));
assert_eq!(maximum_functional(&xs), Some(9));
}
#[test]
fn test_maximum_empty() {
let xs: &[i64] = &[];
assert_eq!(maximum_idiomatic(xs), None);
assert_eq!(maximum_functional(xs), None);
}
#[test]
fn test_maximum_single() {
assert_eq!(maximum_idiomatic(&[42]), Some(42));
assert_eq!(maximum_functional(&[42]), Some(42));
}
#[test]
fn test_reverse() {
let xs = [3, 1, 4, 1, 5, 9, 2, 6];
let expected = vec![6, 2, 9, 5, 1, 4, 1, 3];
assert_eq!(reverse_idiomatic(&xs), expected);
assert_eq!(reverse_functional(&xs), expected);
assert_eq!(reverse_iter_fold(&xs), expected);
}
#[test]
fn test_reverse_empty() {
let xs: &[i64] = &[];
let empty: Vec<i64> = vec![];
assert_eq!(reverse_idiomatic(xs), empty);
assert_eq!(reverse_functional(xs), empty);
assert_eq!(reverse_iter_fold(xs), empty);
}
#[test]
fn test_fold_left_generic() {
// Build a string: "((init+3)+1)+4"
let xs = [3, 1, 4];
let result = fold_left(|acc, x| format!("({acc}+{x})"), "init".to_string(), &xs);
assert_eq!(result, "(((init+3)+1)+4)");
}
}
fn main() {
println!("{:?}", sum_idiomatic(&xs), 31);
println!("{:?}", sum_functional(&xs), 31);
println!("{:?}", sum_iter_fold(&xs), 31);
}
(* fold_left โ Tail-Recursive Accumulator *)
(* Implementation 1: Direct tail-recursive fold_left *)
let rec fold_left f acc = function
| [] -> acc
| h :: t -> fold_left f (f acc h) t
(* Implementation 2: Using List.fold_left from stdlib *)
let fold_left_stdlib f acc lst = List.fold_left f acc lst
(* Classic uses *)
let sum lst = fold_left ( + ) 0 lst
let product lst = fold_left ( * ) 1 lst
let maximum lst = fold_left max (List.hd lst) (List.tl lst)
let reverse lst = fold_left (fun acc x -> x :: acc) [] lst
(* Tests *)
let () =
let nums = [3; 1; 4; 1; 5; 9; 2; 6] in
assert (sum nums = 31);
assert (product nums = 6480);
assert (maximum nums = 9);
assert (reverse [1; 2; 3] = [3; 2; 1]);
assert (sum [] = 0);
assert (reverse [] = []);
Printf.printf "All fold_left tests passed!\n"
๐ Detailed Comparison
Comparison: fold_left โ OCaml vs Rust
OCaml โ Tail-Recursive
๐ช Show OCaml equivalent
let rec fold_left f acc = function
| [] -> acc
| h :: t -> fold_left f (f acc h) t
let sum lst = fold_left ( + ) 0 lst
let product lst = fold_left ( * ) 1 lst
let maximum lst = fold_left max (List.hd lst) (List.tl lst)
let reverse lst = fold_left (fun acc x -> x :: acc) [] lst
Rust โ Idiomatic
pub fn sum_idiomatic(xs: &[i64]) -> i64 { xs.iter().sum() }
pub fn product_idiomatic(xs: &[i64]) -> i64 { xs.iter().product() }
pub fn maximum_idiomatic(xs: &[i64]) -> Option<i64> { xs.iter().copied().max() }
pub fn reverse_idiomatic(xs: &[i64]) -> Vec<i64> {
let mut v = xs.to_vec();
v.reverse();
v
}Rust โ Functional (Custom fold_left)
pub fn fold_left<T, A>(f: impl Fn(A, &T) -> A, mut acc: A, xs: &[T]) -> A {
for x in xs { acc = f(acc, x); }
acc
}
pub fn maximum_functional(xs: &[i64]) -> Option<i64> {
let (&first, rest) = xs.split_first()?;
Some(fold_left(|acc, &x| if x > acc { x } else { acc }, first, rest))
}Comparison Table
| Aspect | OCaml | Rust |
|---|---|---|
| Type signature | `('b -> 'a -> 'b) -> 'b -> 'a list -> 'b` | `fn fold_left<T, A>(f: Fn(A, &T) -> A, acc: A, xs: &[T]) -> A` |
| Tail-call optimization | Guaranteed by compiler | Not guaranteed (we use a loop instead) |
| Empty list max | `List.hd` raises exception | Returns `None` (safe) |
| Reverse mechanism | Cons to front: `x :: acc` | `v.reverse()` in-place or `insert(0, x)` |
| Built-in | `List.fold_left` | `Iterator::fold` |
| Accumulator | Passed by value (GC'd) | Moved by ownership |
Type Signatures Explained
OCaml: `val fold_left : ('b -> 'a -> 'b) -> 'b -> 'a list -> 'b`
- Accumulator type `'b` comes first in the function parameter
- The accumulator is threaded through each recursive call
- `A` is the accumulator type, `T` is the element type
- `&T`: elements are borrowed from the slice
- `mut acc`: the accumulator is mutably rebound on each iteration
Takeaways
1. fold_left is Rust's natural fold โ `Iterator::fold` processes left to right, matching fold_left's semantics exactly 2. Safety improvement: Rust's `maximum` returns `Option` instead of panicking on empty input 3. No TCO needed: Rust's `for` loop is already iterative โ no tail-call optimization concern 4. Ownership makes fold natural โ the accumulator is moved into each step, not shared 5. OCaml's `function` keyword combines `fun` + `match`; Rust uses `match` explicitly or `for` loops