1002 — List Fold Left
Tutorial
The Problem
Implement fold_left — the left-to-right accumulator fold — both as a thin wrapper around Iterator::fold and as an explicit tail-recursive function. Compute sum, product, and maximum from a list using the same fold abstraction. Compare with OCaml's List.fold_left and a custom tail-recursive implementation.
🎯 Learning Outcomes
items.iter().fold(init, f) as the idiomatic Rust foldfold_left_recursive using slice pattern [head, tail @ ..] for educational comparisonfold_left is left-associative: ((init ⊕ x₁) ⊕ x₂) ⊕ …sum, product, and max as specialisations of fold_leftIterator::fold to OCaml's List.fold_left f init lstfold is always tail-recursive; OCaml's recursive version needs manual TCOCode Example
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// Iterator-based fold_left (idiomatic)
let sum = numbers.iter().fold(0, |acc, x| acc + x);
let product = numbers.iter().fold(1, |acc, x| acc * x);
let max_val = numbers.iter().fold(i32::MIN, |acc, x| if x > &acc { *x } else { acc });
println!("Sum: {}, Product: {}, Max: {}", sum, product, max_val);
}Key Differences
| Aspect | Rust | OCaml |
|---|---|---|
| Idiomatic | iter.fold(init, f) | List.fold_left f init lst |
| Argument order | f(accumulator, item) | f acc item |
| Item type | &T (reference from iter()) | T (value from list) |
| Tail recursion | Always (iterative) | TCO by compiler |
| Partial application | Not idiomatic (closure) | let sum = List.fold_left (+) 0 |
| Max with empty | Requires Option<U> or sentinel | Same pattern |
fold_left is the universal list combinator: map, filter, sum, product, reverse, length — all are expressible as fold_left. Understanding it deeply is the foundation of functional list processing.
OCaml Approach
List.fold_left (+) 0 numbers is the standard library call. The custom fold_left_recursive f acc = function | [] -> acc | head :: tail -> fold_left_recursive f (f acc head) tail is tail-recursive and gets TCO by OCaml's compiler. Convenience functions sum and product are partial applications: let sum = List.fold_left (+) 0.
Full Source
#![allow(clippy::all)]
//! # List Fold Left
//!
//! Demonstrates reducing a list to a single value using left-to-right folding.
//! This example shows both iterator-based and recursive approaches in Rust.
/// Fold left using iterators (idiomatic Rust approach).
///
/// Applies a binary operation cumulatively from left to right,
/// starting with an initial accumulator value.
///
/// # Arguments
/// * `init` - Initial accumulator value
/// * `items` - Slice of items to fold
/// * `f` - Binary operation: (accumulator, item) -> accumulator
///
/// # Example
/// ```
/// use list_fold_left::fold_left_iter;
///
/// let numbers = vec![1, 2, 3, 4, 5];
/// let sum = fold_left_iter(0, &numbers, |acc, x| acc + x);
/// assert_eq!(sum, 15);
/// ```
pub fn fold_left_iter<T, U, F>(init: U, items: &[T], f: F) -> U
where
F: Fn(U, &T) -> U,
{
items.iter().fold(init, f)
}
/// Fold left using recursion (functional style, like OCaml).
///
/// Tail-recursive implementation that manually processes the list.
/// The compiler may optimize this into a loop.
///
/// # Arguments
/// * `init` - Initial accumulator value
/// * `items` - Slice of items to fold
/// * `f` - Binary operation: (accumulator, item) -> accumulator
///
/// # Example
/// ```
/// use list_fold_left::fold_left_recursive;
///
/// let numbers = vec![1, 2, 3, 4, 5];
/// let sum = fold_left_recursive(0, &numbers, |acc, x| acc + x);
/// assert_eq!(sum, 15);
/// ```
pub fn fold_left_recursive<T, U, F>(init: U, items: &[T], f: F) -> U
where
F: Fn(U, &T) -> U,
{
match items {
[] => init,
[head, tail @ ..] => fold_left_recursive(f(init, head), tail, f),
}
}
/// Calculate the sum of a list.
///
/// # Example
/// ```
/// use list_fold_left::sum;
/// assert_eq!(sum(&[1, 2, 3, 4, 5]), 15);
/// ```
pub fn sum(items: &[i32]) -> i32 {
fold_left_iter(0, items, |acc, x| acc + x)
}
/// Calculate the product of a list.
///
/// # Example
/// ```
/// use list_fold_left::product;
/// assert_eq!(product(&[1, 2, 3, 4, 5]), 120);
/// ```
pub fn product(items: &[i32]) -> i32 {
fold_left_iter(1, items, |acc, x| acc * x)
}
/// Find the maximum value in a list.
///
/// Returns `i32::MIN` if the list is empty (matching OCaml's min_int).
///
/// # Example
/// ```
/// use list_fold_left::max_value;
/// assert_eq!(max_value(&[1, 5, 3, 2, 4]), 5);
/// ```
pub fn max_value(items: &[i32]) -> i32 {
fold_left_iter(i32::MIN, items, |acc, x| if x > &acc { *x } else { acc })
}
/// Find the minimum value in a list.
///
/// Returns `i32::MAX` if the list is empty.
///
/// # Example
/// ```
/// use list_fold_left::min_value;
/// assert_eq!(min_value(&[1, 5, 3, 2, 4]), 1);
/// ```
pub fn min_value(items: &[i32]) -> i32 {
fold_left_iter(i32::MAX, items, |acc, x| if x < &acc { *x } else { acc })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fold_left_iter_sum() {
let numbers = vec![1, 2, 3, 4, 5];
assert_eq!(fold_left_iter(0, &numbers, |acc, x| acc + x), 15);
}
#[test]
fn test_fold_left_iter_product() {
let numbers = vec![1, 2, 3, 4, 5];
assert_eq!(fold_left_iter(1, &numbers, |acc, x| acc * x), 120);
}
#[test]
fn test_fold_left_iter_max() {
let numbers = vec![1, 5, 3, 2, 4];
assert_eq!(
fold_left_iter(i32::MIN, &numbers, |acc, x| if x > &acc { *x } else { acc }),
5
);
}
#[test]
fn test_fold_left_iter_empty() {
let empty: Vec<i32> = vec![];
assert_eq!(fold_left_iter(0, &empty, |acc, x| acc + x), 0);
assert_eq!(fold_left_iter(1, &empty, |acc, x| acc * x), 1);
}
#[test]
fn test_fold_left_iter_single() {
let single = vec![42];
assert_eq!(fold_left_iter(0, &single, |acc, x| acc + x), 42);
assert_eq!(fold_left_iter(1, &single, |acc, x| acc * x), 42);
}
#[test]
fn test_fold_left_recursive_sum() {
let numbers = vec![1, 2, 3, 4, 5];
assert_eq!(fold_left_recursive(0, &numbers, |acc, x| acc + x), 15);
}
#[test]
fn test_fold_left_recursive_product() {
let numbers = vec![1, 2, 3, 4, 5];
assert_eq!(fold_left_recursive(1, &numbers, |acc, x| acc * x), 120);
}
#[test]
fn test_fold_left_recursive_max() {
let numbers = vec![1, 5, 3, 2, 4];
assert_eq!(
fold_left_recursive(i32::MIN, &numbers, |acc, x| if x > &acc { *x } else { acc }),
5
);
}
#[test]
fn test_fold_left_recursive_empty() {
let empty: Vec<i32> = vec![];
assert_eq!(fold_left_recursive(0, &empty, |acc, x| acc + x), 0);
}
#[test]
fn test_fold_left_recursive_single() {
let single = vec![42];
assert_eq!(fold_left_recursive(0, &single, |acc, x| acc + x), 42);
}
#[test]
fn test_sum() {
assert_eq!(sum(&[1, 2, 3, 4, 5]), 15);
assert_eq!(sum(&[]), 0);
assert_eq!(sum(&[42]), 42);
assert_eq!(sum(&[-5, 10, -3]), 2);
}
#[test]
fn test_product() {
assert_eq!(product(&[1, 2, 3, 4, 5]), 120);
assert_eq!(product(&[]), 1);
assert_eq!(product(&[42]), 42);
assert_eq!(product(&[2, 3, 4]), 24);
}
#[test]
fn test_max_value() {
assert_eq!(max_value(&[1, 5, 3, 2, 4]), 5);
assert_eq!(max_value(&[]), i32::MIN);
assert_eq!(max_value(&[42]), 42);
assert_eq!(max_value(&[-5, -1, -10]), -1);
}
#[test]
fn test_min_value() {
assert_eq!(min_value(&[1, 5, 3, 2, 4]), 1);
assert_eq!(min_value(&[]), i32::MAX);
assert_eq!(min_value(&[42]), 42);
assert_eq!(min_value(&[-5, -1, -10]), -10);
}
#[test]
fn test_fold_left_iter_string_concat() {
let words = vec!["Hello", " ", "World"];
let result = fold_left_iter(String::new(), &words, |acc, word| acc + word);
assert_eq!(result, "Hello World");
}
#[test]
fn test_fold_left_recursive_string_concat() {
let words = vec!["Hello", " ", "World"];
let result = fold_left_recursive(String::new(), &words, |acc, word| acc + word);
assert_eq!(result, "Hello World");
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fold_left_iter_sum() {
let numbers = vec![1, 2, 3, 4, 5];
assert_eq!(fold_left_iter(0, &numbers, |acc, x| acc + x), 15);
}
#[test]
fn test_fold_left_iter_product() {
let numbers = vec![1, 2, 3, 4, 5];
assert_eq!(fold_left_iter(1, &numbers, |acc, x| acc * x), 120);
}
#[test]
fn test_fold_left_iter_max() {
let numbers = vec![1, 5, 3, 2, 4];
assert_eq!(
fold_left_iter(i32::MIN, &numbers, |acc, x| if x > &acc { *x } else { acc }),
5
);
}
#[test]
fn test_fold_left_iter_empty() {
let empty: Vec<i32> = vec![];
assert_eq!(fold_left_iter(0, &empty, |acc, x| acc + x), 0);
assert_eq!(fold_left_iter(1, &empty, |acc, x| acc * x), 1);
}
#[test]
fn test_fold_left_iter_single() {
let single = vec![42];
assert_eq!(fold_left_iter(0, &single, |acc, x| acc + x), 42);
assert_eq!(fold_left_iter(1, &single, |acc, x| acc * x), 42);
}
#[test]
fn test_fold_left_recursive_sum() {
let numbers = vec![1, 2, 3, 4, 5];
assert_eq!(fold_left_recursive(0, &numbers, |acc, x| acc + x), 15);
}
#[test]
fn test_fold_left_recursive_product() {
let numbers = vec![1, 2, 3, 4, 5];
assert_eq!(fold_left_recursive(1, &numbers, |acc, x| acc * x), 120);
}
#[test]
fn test_fold_left_recursive_max() {
let numbers = vec![1, 5, 3, 2, 4];
assert_eq!(
fold_left_recursive(i32::MIN, &numbers, |acc, x| if x > &acc { *x } else { acc }),
5
);
}
#[test]
fn test_fold_left_recursive_empty() {
let empty: Vec<i32> = vec![];
assert_eq!(fold_left_recursive(0, &empty, |acc, x| acc + x), 0);
}
#[test]
fn test_fold_left_recursive_single() {
let single = vec![42];
assert_eq!(fold_left_recursive(0, &single, |acc, x| acc + x), 42);
}
#[test]
fn test_sum() {
assert_eq!(sum(&[1, 2, 3, 4, 5]), 15);
assert_eq!(sum(&[]), 0);
assert_eq!(sum(&[42]), 42);
assert_eq!(sum(&[-5, 10, -3]), 2);
}
#[test]
fn test_product() {
assert_eq!(product(&[1, 2, 3, 4, 5]), 120);
assert_eq!(product(&[]), 1);
assert_eq!(product(&[42]), 42);
assert_eq!(product(&[2, 3, 4]), 24);
}
#[test]
fn test_max_value() {
assert_eq!(max_value(&[1, 5, 3, 2, 4]), 5);
assert_eq!(max_value(&[]), i32::MIN);
assert_eq!(max_value(&[42]), 42);
assert_eq!(max_value(&[-5, -1, -10]), -1);
}
#[test]
fn test_min_value() {
assert_eq!(min_value(&[1, 5, 3, 2, 4]), 1);
assert_eq!(min_value(&[]), i32::MAX);
assert_eq!(min_value(&[42]), 42);
assert_eq!(min_value(&[-5, -1, -10]), -10);
}
#[test]
fn test_fold_left_iter_string_concat() {
let words = vec!["Hello", " ", "World"];
let result = fold_left_iter(String::new(), &words, |acc, word| acc + word);
assert_eq!(result, "Hello World");
}
#[test]
fn test_fold_left_recursive_string_concat() {
let words = vec!["Hello", " ", "World"];
let result = fold_left_recursive(String::new(), &words, |acc, word| acc + word);
assert_eq!(result, "Hello World");
}
}
Deep Comparison
Comparison: OCaml vs Rust - Fold Left
Side-by-Side Code Comparison
The Original OCaml Example
(* Data and basic operations *)
let numbers = [1; 2; 3; 4; 5]
let sum = List.fold_left ( + ) 0 numbers
let product = List.fold_left ( * ) 1 numbers
let max_val = List.fold_left max min_int numbers
(* Output *)
let () = Printf.printf "Sum: %d, Product: %d, Max: %d\n" sum product max_val
The Rust Iterator Equivalent
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// Iterator-based fold_left (idiomatic)
let sum = numbers.iter().fold(0, |acc, x| acc + x);
let product = numbers.iter().fold(1, |acc, x| acc * x);
let max_val = numbers.iter().fold(i32::MIN, |acc, x| if x > &acc { *x } else { acc });
println!("Sum: {}, Product: {}, Max: {}", sum, product, max_val);
}
The Rust Recursive Equivalent
fn fold_left_recursive<T, U, F>(init: U, items: &[T], f: F) -> U
where
F: Fn(U, &T) -> U,
{
match items {
[] => init,
[head, tail @ ..] => fold_left_recursive(f(init, head), tail, f),
}
}
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// Recursive fold_left (functional style)
let sum = fold_left_recursive(0, &numbers, |acc, x| acc + x);
let product = fold_left_recursive(1, &numbers, |acc, x| acc * x);
let max_val = fold_left_recursive(i32::MIN, &numbers, |acc, x| if x > &acc { *x } else { acc });
println!("Sum: {}, Product: {}, Max: {}", sum, product, max_val);
}
Type Signatures Table
| Operation | OCaml | Rust (Iterator) | Rust (Recursive) |
|---|---|---|---|
| Sum | int list -> int | fn(Vec<i32>) -> i32 | fn(U, &[T], Fn(U, &T)->U) -> U |
| Generic Fold | ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a | fn<T,U,F>(U, &[T], F)->U where F:Fn(U,&T)->U | Same as iterator |
| Max | (int -> int -> int) -> int -> int list -> int | fn(i32::MIN, &[i32], \|acc, x\| ...) -> i32 | Same as iterator |
Key Insights
1. Currying vs Direct Application
OCaml embraces currying:
List.fold_left : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a
Each arrow represents a curried parameter. You can partially apply:
let fold_add = List.fold_left ( + )
let sum = fold_add 0 numbers
Rust uses direct function application:
fn fold_left_iter<T, U, F>(init: U, items: &[T], f: F) -> U
All parameters at once. Closures capture context naturally:
let init = 0;
let result = fold_left_iter(init, &numbers, |acc, x| acc + x);
Winner: OCaml is more elegant for composition, but Rust's approach is more explicit and easier to read for newcomers.
2. Memory Model Impact
OCaml:
Rust:
fn fold_left_recursive<T, U, F>(init: U, items: &[T], f: F) -> U {
match items {
[] => init,
[head, tail @ ..] => fold_left_recursive(f(init, head), tail, f),
}
}
F and T don't outlive dataWinner: Rust is more efficient for large arrays thanks to slices. OCaml is safer from stack overflow due to list structure.
3. Operator Overloading vs Explicit Syntax
OCaml:
List.fold_left ( + ) 0 numbers (* Treat + as a function *)
List.fold_left max min_int numbers (* Use max builtin directly *)
Rust:
fold_left_iter(0, &numbers, |acc, x| acc + x) (* Must write closure *)
fold_left_iter(i32::MIN, &numbers, |acc, x| if x > &acc { *x } else { acc })
Winner: OCaml is concise. Rust is explicit (which some find clearer, especially beginners).
4. Generic Type Constraints
OCaml:
let fold_left f init xs = List.fold_left f init xs
(* Type checker infers: 'a -> 'b -> 'a *)
(* Works with ANY types *)
Rust:
pub fn fold_left_iter<T, U, F>(init: U, items: &[T], f: F) -> U
where
F: Fn(U, &T) -> U,
{
items.iter().fold(init, f)
}
T = element typeU = accumulator type (can differ from T)F = closure type with trait bound Fn(U, &T) -> UWinner: Rust wins on clarity—you see exactly what types are involved and what the function expects.
5. Tail Call Optimization (TCO)
OCaml:
let rec fold_left f acc = function
| [] -> acc
| head :: tail -> fold_left f (f acc head) tail
This is guaranteed tail-recursive. The recursive call is in tail position, so OCaml (and most Lisp/Scheme implementations) optimize it to a loop.
Rust:
match items {
[] => init,
[head, tail @ ..] => fold_left_recursive(f(init, head), tail, f),
}
This is tail-recursive in structure but Rust makes NO GUARANTEE of TCO. The compiler may optimize it, but:
items.iter().fold() is always loop-compiled and safeWinner: OCaml wins on predictability. Rust wins on practical safety (use iterators!).
Performance Implications
Iterator-Based (Rust)
Recursive (Both Languages)
Summary: When to Use Each
| Scenario | Language | Approach | Reason |
|---|---|---|---|
| Large arrays (>10K) | Rust | Iterator | Stack-safe, cache-friendly |
| Beautiful code | OCaml | fold_left | Concise, elegant |
| Learning recursion | Rust | Recursive | Explicit pattern matching |
| Production system | Rust | Iterator | Predictable performance |
| Functional composition | OCaml | Standard lib | Currying + pipelines |
| Type-safe pipelines | Rust | Closures + chains | Iterator adapters |
Both languages excel at expressing fold operations. OCaml is more concise; Rust is more explicit and safer for large-scale data processing.
Exercises
map using fold_left_iter by collecting transformed elements into a Vec.filter using fold_left_iter by conditionally appending to the accumulator.reverse using fold_left_iter with a Vec accumulator using insert(0, ...).fold_right (right-associative fold) and show that fold_right cons [] lst reconstructs the list.List.fold_left from scratch without using the standard library version, then verify it matches List.fold_left on 10 example inputs.