097 — Flatten and Flat Map
Tutorial
The Problem
Use Rust's .flatten() to collapse an iterator of iterables into a single flat stream, and .flat_map(f) to map then flatten in one step. Demonstrate on nested vectors, optional values (Vec<Option<T>>), and a transformation that expands each element into a mini-sequence. Compare with OCaml's List.concat and List.concat_map.
🎯 Learning Outcomes
.flatten() on any iterator whose Item: IntoIterator.flat_map(f) as shorthand for .map(f).flatten()Vec<Option<T>> to filter out None values (since Option implements IntoIterator).flatten() to OCaml's List.concat and flat_map to List.concat_mapCode Example
#![allow(clippy::all)]
// 097: Flatten and Flat Map
#[cfg(test)]
mod tests {
#[test]
fn test_flatten() {
let v: Vec<i32> = vec![vec![1, 2], vec![3, 4], vec![5]]
.into_iter()
.flatten()
.collect();
assert_eq!(v, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_flat_map() {
let v: Vec<i32> = [1, 2, 3].iter().flat_map(|&x| vec![x, x * 10]).collect();
assert_eq!(v, vec![1, 10, 2, 20, 3, 30]);
}
#[test]
fn test_flatten_empty() {
let v: Vec<i32> = vec![vec![], vec![1], vec![], vec![2, 3]]
.into_iter()
.flatten()
.collect();
assert_eq!(v, vec![1, 2, 3]);
}
#[test]
fn test_flatten_options() {
let v: Vec<i32> = [Some(1), None, Some(3)].iter().flatten().copied().collect();
assert_eq!(v, vec![1, 3]);
}
}Key Differences
| Aspect | Rust | OCaml |
|---|---|---|
| Flatten | .flatten() | List.concat |
| Flat map | .flat_map(f) | List.concat_map f |
| Option filter | .flatten() on Option<T> iter | List.filter_map |
| Laziness | Lazy (no intermediate collection) | List.concat is eager |
| Generality | Any IntoIterator | Lists only (without Seq) |
| Monadic bind | flat_map = bind for Iterator | Same semantics |
flat_map is the monadic bind operation for the iterator/list monad. Any time you write .map(f) where f returns a Vec or Option, and then immediately .flatten(), you can replace both with .flat_map(f).
OCaml Approach
List.concat concatenates a list of lists. List.concat_map f lst maps f and concatenates. Seq.flat_map does the lazy equivalent. OCaml's approach is simpler syntactically; Rust's .flatten() is more general — it works on any Item: IntoIterator, not just nested lists.
Full Source
#![allow(clippy::all)]
// 097: Flatten and Flat Map
#[cfg(test)]
mod tests {
#[test]
fn test_flatten() {
let v: Vec<i32> = vec![vec![1, 2], vec![3, 4], vec![5]]
.into_iter()
.flatten()
.collect();
assert_eq!(v, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_flat_map() {
let v: Vec<i32> = [1, 2, 3].iter().flat_map(|&x| vec![x, x * 10]).collect();
assert_eq!(v, vec![1, 10, 2, 20, 3, 30]);
}
#[test]
fn test_flatten_empty() {
let v: Vec<i32> = vec![vec![], vec![1], vec![], vec![2, 3]]
.into_iter()
.flatten()
.collect();
assert_eq!(v, vec![1, 2, 3]);
}
#[test]
fn test_flatten_options() {
let v: Vec<i32> = [Some(1), None, Some(3)].iter().flatten().copied().collect();
assert_eq!(v, vec![1, 3]);
}
}#[cfg(test)]
mod tests {
#[test]
fn test_flatten() {
let v: Vec<i32> = vec![vec![1, 2], vec![3, 4], vec![5]]
.into_iter()
.flatten()
.collect();
assert_eq!(v, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_flat_map() {
let v: Vec<i32> = [1, 2, 3].iter().flat_map(|&x| vec![x, x * 10]).collect();
assert_eq!(v, vec![1, 10, 2, 20, 3, 30]);
}
#[test]
fn test_flatten_empty() {
let v: Vec<i32> = vec![vec![], vec![1], vec![], vec![2, 3]]
.into_iter()
.flatten()
.collect();
assert_eq!(v, vec![1, 2, 3]);
}
#[test]
fn test_flatten_options() {
let v: Vec<i32> = [Some(1), None, Some(3)].iter().flatten().copied().collect();
assert_eq!(v, vec![1, 3]);
}
}
Deep Comparison
Core Insight
Flatten removes one level of nesting; flat_map combines map + flatten in one step
OCaml Approach
Rust Approach
Comparison Table
| Feature | OCaml | Rust |
|---|---|---|
| See | example.ml | example.rs |
Exercises
flat_map to split a sentence into individual words: sentences.iter().flat_map(|s| s.split_whitespace()).my_flatten<T>(v: Vec<Vec<T>>) -> Vec<T> without using .flatten() — use fold instead..flatten() on Vec<Result<T, E>> — note it only works with iter_ok-style logic; investigate why.expand_range(ranges: &[(i32, i32)]) -> Vec<i32> that flattens each range into its elements.flat_map_seq : ('a -> 'b Seq.t) -> 'a Seq.t -> 'b Seq.t for lazy flat mapping.