115: Vec Operations Functionally
Difficulty: 2 Level: Intermediate Rust's iterator adapters (`map`, `filter`, `fold`, `flat_map`) translate OCaml's list operations directly โ with lazy evaluation and zero intermediate allocation.The Problem This Solves
Imperative loops work, but they obscure intent. A `for` loop that filters some items, transforms others, and accumulates a result puts the how (loop, counter, conditional append) in the way of the what (select items matching X, transform by Y, reduce to Z). Bugs hide in the bookkeeping. Python has list comprehensions and `map`/`filter`, but they're eager โ every intermediate result is a full list in memory. Chain three operations and you have three lists allocated. For large datasets, this matters. Java's streams are lazy but verbose. And languages without an ownership model have to decide: does `map` copy the collection or share it? Does the caller own the transformed result? Rust's iterator system is lazy and zero-cost. `vec.iter().map(f).filter(g)` allocates nothing until you call `.collect()` or consume the iterator. Each element flows through the entire pipeline in one pass. The resulting code reads like a data pipeline description โ what you're doing, not how you're doing it โ and compiles to code as efficient as a hand-written loop.The Intuition
Rust's iterator adapters are lazy transformations that chain without allocating intermediate collections โ call `.collect()` at the end to materialize the result exactly once.How It Works in Rust
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// OCaml: List.map (fun x -> x * x) lst
let squares: Vec<i32> = numbers.iter()
.map(|&x| x * x)
.collect();
// OCaml: List.filter (fun x -> x mod 2 = 0) lst
let evens: Vec<i32> = numbers.iter()
.filter(|&&x| x % 2 == 0)
.copied()
.collect();
// OCaml: List.fold_left (+) 0 lst
let sum: i32 = numbers.iter().sum();
let sum2: i32 = numbers.iter().fold(0, |acc, &x| acc + x);
// OCaml: List.filter_map
let parsed: Vec<i32> = vec!["1", "two", "3", "four", "5"]
.iter()
.filter_map(|s| s.parse().ok())
.collect();
// [1, 3, 5]
// OCaml: List.flatten (List.map f lst)
let flat: Vec<i32> = vec![vec![1, 2], vec![3, 4], vec![5]]
.into_iter()
.flatten()
.collect();
// flat_map = map + flatten
let words = vec!["hello world", "foo bar"];
let all_words: Vec<&str> = words.iter()
.flat_map(|s| s.split_whitespace())
.collect();
// Chaining multiple operations โ one pass, zero intermediate allocations
let result: Vec<String> = numbers.iter()
.filter(|&&x| x % 2 == 0) // keep evens
.map(|&x| x * x) // square them
.filter(|&x| x > 10) // keep > 10
.map(|x| format!("{}!", x)) // format as strings
.collect();
// ["16!", "36!", "64!", "100!"]
// zip, enumerate, take, skip
let paired: Vec<(i32, i32)> = numbers.iter()
.copied()
.zip(numbers.iter().copied().rev())
.take(3)
.collect();
// [(1, 10), (2, 9), (3, 8)]
// into_iter() consumes the Vec; iter() borrows
let owned_result: Vec<i32> = numbers.into_iter()
.map(|x| x * 2)
.collect();
// numbers is moved โ can't use it again
What This Unlocks
- One-pass laziness โ chained iterators process each element once through the entire pipeline; no intermediate `Vec` allocations regardless of chain length.
- Composable pipelines โ each adapter (`map`, `filter`, `flat_map`) is a pure transformation; the pipeline describes intent, not implementation.
- Ownership clarity โ `.iter()` borrows, `.iter_mut()` borrows mutably, `.into_iter()` consumes; the choice is explicit and the compiler enforces it.
Key Differences
| Concept | OCaml | Rust |
|---|---|---|
| Map | `List.map f lst` | `vec.iter().map(f).collect()` |
| Filter | `List.filter pred lst` | `vec.iter().filter(pred).collect()` |
| Fold | `List.fold_left f init lst` | `vec.iter().fold(init, f)` |
| Flat map | `List.concat_map f lst` | `vec.iter().flat_map(f).collect()` |
| Laziness | Eager (lists) / Lazy (Seq) | Always lazy โ evaluated on `.collect()` or consumption |
| Intermediate allocations | One per operation | Zero โ single pass through the pipeline |
// Example 115: Vec Operations Functionally
// OCaml list functions โ Rust Vec/iterator methods
// Approach 1: map, filter, fold via iterators
fn approach1() {
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let sum: i32 = data.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * 2)
.sum();
assert_eq!(sum, 60);
println!("Sum of doubled evens: {}", sum);
}
// Approach 2: zip, unzip, partition
fn approach2() {
let names = vec!["Alice", "Bob", "Charlie"];
let ages = vec![30, 25, 35];
let pairs: Vec<_> = names.iter().zip(ages.iter()).collect();
let (young, old): (Vec<_>, Vec<_>) = pairs.iter()
.partition(|(_, &&age)| age < 30);
assert_eq!(young.len(), 1);
assert_eq!(old.len(), 2);
println!("Young: {}, Old: {}", young.len(), old.len());
}
// Approach 3: flat_map, scan, windows
fn approach3() {
// flat_map
let nested = vec![vec![1, 2], vec![3], vec![4, 5, 6]];
let flat: Vec<i32> = nested.into_iter().flatten().collect();
assert_eq!(flat, vec![1, 2, 3, 4, 5, 6]);
// scan (running accumulation)
let running: Vec<i32> = [1, 2, 3, 4, 5].iter()
.scan(0, |state, &x| { *state += x; Some(*state) })
.collect();
assert_eq!(running, vec![1, 3, 6, 10, 15]);
// windows
let data = vec![1, 2, 3, 4, 5];
let pairs: Vec<_> = data.windows(2).map(|w| (w[0], w[1])).collect();
assert_eq!(pairs, vec![(1,2), (2,3), (3,4), (4,5)]);
println!("Flat: {:?}", flat);
println!("Running: {:?}", running);
println!("Windows: {:?}", pairs);
}
fn main() {
println!("=== Approach 1: Map/Filter/Fold ===");
approach1();
println!("\n=== Approach 2: Zip/Partition ===");
approach2();
println!("\n=== Approach 3: FlatMap/Scan/Windows ===");
approach3();
}
#[cfg(test)]
mod tests {
#[test]
fn test_filter_map() {
let v: Vec<i32> = (1..=5).filter(|x| x % 2 == 1).collect();
assert_eq!(v, vec![1, 3, 5]);
}
#[test]
fn test_fold() {
let product: i32 = vec![1, 2, 3, 4].iter().product();
assert_eq!(product, 24);
}
#[test]
fn test_zip_unzip() {
let (a, b): (Vec<_>, Vec<_>) = vec![(1, "a"), (2, "b")].into_iter().unzip();
assert_eq!(a, vec![1, 2]);
assert_eq!(b, vec!["a", "b"]);
}
#[test]
fn test_flat_map() {
let v: Vec<i32> = vec![1, 2, 3].into_iter()
.flat_map(|x| vec![x, x * 10])
.collect();
assert_eq!(v, vec![1, 10, 2, 20, 3, 30]);
}
#[test]
fn test_chunks() {
let v = vec![1, 2, 3, 4, 5];
let chunks: Vec<&[i32]> = v.chunks(2).collect();
assert_eq!(chunks, vec![&[1, 2][..], &[3, 4][..], &[5][..]]);
}
}
(* Example 115: Vec Operations Functionally โ OCaml List Functions โ Rust *)
(* Approach 1: map, filter, fold *)
let approach1 () =
let data = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10] in
let evens = List.filter (fun x -> x mod 2 = 0) data in
let doubled = List.map (fun x -> x * 2) evens in
let sum = List.fold_left ( + ) 0 doubled in
assert (sum = 60);
Printf.printf "Sum of doubled evens: %d\n" sum
(* Approach 2: zip, unzip, partition *)
let zip a b = List.map2 (fun x y -> (x, y)) a b
let unzip lst = (List.map fst lst, List.map snd lst)
let approach2 () =
let names = ["Alice"; "Bob"; "Charlie"] in
let ages = [30; 25; 35] in
let pairs = zip names ages in
let (young, old) = List.partition (fun (_, age) -> age < 30) pairs in
assert (List.length young = 1);
assert (List.length old = 2);
Printf.printf "Young: %d, Old: %d\n" (List.length young) (List.length old)
(* Approach 3: flat_map, scan, windows *)
let flat_map f lst = List.concat (List.map f lst)
let scan f init lst =
let rec go acc state = function
| [] -> List.rev acc
| x :: rest ->
let next = f state x in
go (next :: acc) next rest
in go [init] init lst
let approach3 () =
let nested = [[1;2]; [3]; [4;5;6]] in
let flat = flat_map (fun x -> x) nested in
assert (flat = [1;2;3;4;5;6]);
let running = scan ( + ) 0 [1;2;3;4;5] in
assert (running = [0;1;3;6;10;15]);
Printf.printf "Flat: %s\n" (String.concat "," (List.map string_of_int flat))
let () =
approach1 ();
approach2 ();
approach3 ();
Printf.printf "โ All tests passed\n"
๐ Detailed Comparison
Comparison: Vec Operations
Map/Filter/Fold
OCaml:
๐ช Show OCaml equivalent
let result = List.fold_left ( + ) 0
(List.map (fun x -> x * 2)
(List.filter (fun x -> x mod 2 = 0) data))
Rust:
let result: i32 = data.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * 2)
.sum();Zip
OCaml:
๐ช Show OCaml equivalent
let pairs = List.map2 (fun a b -> (a, b)) names ages
Rust:
let pairs: Vec<_> = names.iter().zip(ages.iter()).collect();Flat Map
OCaml:
๐ช Show OCaml equivalent
let flat = List.concat (List.map f nested)
Rust:
let flat: Vec<_> = nested.into_iter().flatten().collect();
// or: nested.into_iter().flat_map(|x| x).collect()