911-iterator-flatten — Iterator Flatten
Tutorial
The Problem
Nested collections arise naturally: a document is a list of paragraphs, each paragraph is a list of sentences, each sentence is a list of words. Collapsing one level of this nesting — "all words in the document" — is the flatten operation. Formally, it is the monadic join from category theory: join :: m (m a) -> m a. Haskell's concat, OCaml's List.concat, and Rust's .flatten() all implement this. Understanding flatten as join reveals why flat_map = map + flatten = monadic bind (>>=): it is the fundamental operation of every monad.
🎯 Learning Outcomes
.flatten() to remove exactly one level of iterator nesting.flatten() is the monadic join and .flat_map() is monadic bind.flatten() to Vec<Option<T>> using Option's IntoIterator implementationOption::flatten() to collapse Option<Option<T>> to Option<T>List.concat and Option.joinCode Example
// Flatten Vec<Vec<T>> — one level removed
let flat: Vec<i32> = vec![vec![1, 2], vec![3, 4], vec![5, 6]]
.into_iter()
.flatten()
.collect();
// → [1, 2, 3, 4, 5, 6]
// Flatten Vec<Option<T>> — Nones discarded
let values: Vec<i32> = vec![Some(1), None, Some(3), None, Some(5)]
.into_iter()
.flatten()
.collect();
// → [1, 3, 5]Key Differences
Option<T> implements IntoIterator (0 or 1 elements), enabling uniform .flatten() over Vec<Option<T>>; OCaml requires List.filter_map id..flatten() and List.concat remove exactly one level — no recursive deep-flattening.join/concat functions per type..flatten() is lazy; OCaml List.concat allocates eagerly.OCaml Approach
List.concat: 'a list list -> 'a list flattens one level. Option.join: 'a option option -> 'a option (since 4.08) flattens nested options. List.filter_map Some xs = xs is the identity — OCaml uses List.filter_map for optional values rather than "Option as list." List.concat_map f xs = List.concat (List.map f xs) is the flat_map equivalent.
Full Source
#![allow(clippy::all)]
//! 272. One-level flattening with flatten()
//!
//! `flatten()` removes exactly one level of iterator nesting.
//! It is the monadic `join` — if `flat_map` is `bind`, `flatten` is `join`.
/// Flatten a `Vec<Vec<T>>` into a `Vec<T>` — one level of nesting removed.
pub fn flatten_vecs<T>(nested: Vec<Vec<T>>) -> Vec<T> {
nested.into_iter().flatten().collect()
}
/// Flatten an iterator of `Option<T>` — keeps only `Some` values.
/// Each `Option` is itself iterable (yields 0 or 1 items).
pub fn flatten_options<T>(opts: impl IntoIterator<Item = Option<T>>) -> Vec<T> {
opts.into_iter().flatten().collect()
}
/// Flatten `Option<Option<T>>` → `Option<T>`.
/// The inner `None` or outer `None` both produce `None`.
pub fn flatten_option_option<T>(opt: Option<Option<T>>) -> Option<T> {
opt.flatten()
}
/// Flatten character-level: split words into their constituent chars.
pub fn words_to_chars(words: &[&str]) -> Vec<char> {
words.iter().flat_map(|w| w.chars()).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_flatten_vecs_basic() {
let nested = vec![vec![1i32, 2], vec![3, 4], vec![5, 6]];
assert_eq!(flatten_vecs(nested), vec![1, 2, 3, 4, 5, 6]);
}
#[test]
fn test_flatten_vecs_empty_inner() {
let nested: Vec<Vec<i32>> = vec![vec![], vec![1, 2], vec![], vec![3]];
assert_eq!(flatten_vecs(nested), vec![1, 2, 3]);
}
#[test]
fn test_flatten_vecs_all_empty() {
let nested: Vec<Vec<i32>> = vec![vec![], vec![]];
assert_eq!(flatten_vecs(nested), vec![]);
}
#[test]
fn test_flatten_options_filters_none() {
let opts = vec![Some(1), None, Some(3), None, Some(5)];
assert_eq!(flatten_options(opts), vec![1, 3, 5]);
}
#[test]
fn test_flatten_options_all_none() {
let opts: Vec<Option<i32>> = vec![None, None];
assert_eq!(flatten_options(opts), vec![]);
}
#[test]
fn test_flatten_options_all_some() {
let opts = vec![Some(10), Some(20), Some(30)];
assert_eq!(flatten_options(opts), vec![10, 20, 30]);
}
#[test]
fn test_flatten_option_option_some_some() {
assert_eq!(flatten_option_option(Some(Some(42i32))), Some(42));
}
#[test]
fn test_flatten_option_option_some_none() {
assert_eq!(flatten_option_option(Some(None::<i32>)), None);
}
#[test]
fn test_flatten_option_option_none() {
assert_eq!(flatten_option_option(None::<Option<i32>>), None);
}
#[test]
fn test_words_to_chars() {
let words = vec!["hi", "yo"];
assert_eq!(words_to_chars(&words), vec!['h', 'i', 'y', 'o']);
}
#[test]
fn test_flatten_one_level_only() {
// flatten removes exactly ONE level — a Vec<Vec<Vec<T>>> becomes Vec<Vec<T>>
let triple: Vec<Vec<Vec<i32>>> = vec![vec![vec![1, 2], vec![3]], vec![vec![4]]];
let one_less: Vec<Vec<i32>> = triple.into_iter().flatten().collect();
assert_eq!(one_less, vec![vec![1, 2], vec![3], vec![4]]);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_flatten_vecs_basic() {
let nested = vec![vec![1i32, 2], vec![3, 4], vec![5, 6]];
assert_eq!(flatten_vecs(nested), vec![1, 2, 3, 4, 5, 6]);
}
#[test]
fn test_flatten_vecs_empty_inner() {
let nested: Vec<Vec<i32>> = vec![vec![], vec![1, 2], vec![], vec![3]];
assert_eq!(flatten_vecs(nested), vec![1, 2, 3]);
}
#[test]
fn test_flatten_vecs_all_empty() {
let nested: Vec<Vec<i32>> = vec![vec![], vec![]];
assert_eq!(flatten_vecs(nested), vec![]);
}
#[test]
fn test_flatten_options_filters_none() {
let opts = vec![Some(1), None, Some(3), None, Some(5)];
assert_eq!(flatten_options(opts), vec![1, 3, 5]);
}
#[test]
fn test_flatten_options_all_none() {
let opts: Vec<Option<i32>> = vec![None, None];
assert_eq!(flatten_options(opts), vec![]);
}
#[test]
fn test_flatten_options_all_some() {
let opts = vec![Some(10), Some(20), Some(30)];
assert_eq!(flatten_options(opts), vec![10, 20, 30]);
}
#[test]
fn test_flatten_option_option_some_some() {
assert_eq!(flatten_option_option(Some(Some(42i32))), Some(42));
}
#[test]
fn test_flatten_option_option_some_none() {
assert_eq!(flatten_option_option(Some(None::<i32>)), None);
}
#[test]
fn test_flatten_option_option_none() {
assert_eq!(flatten_option_option(None::<Option<i32>>), None);
}
#[test]
fn test_words_to_chars() {
let words = vec!["hi", "yo"];
assert_eq!(words_to_chars(&words), vec!['h', 'i', 'y', 'o']);
}
#[test]
fn test_flatten_one_level_only() {
// flatten removes exactly ONE level — a Vec<Vec<Vec<T>>> becomes Vec<Vec<T>>
let triple: Vec<Vec<Vec<i32>>> = vec![vec![vec![1, 2], vec![3]], vec![vec![4]]];
let one_less: Vec<Vec<i32>> = triple.into_iter().flatten().collect();
assert_eq!(one_less, vec![vec![1, 2], vec![3], vec![4]]);
}
}
Deep Comparison
OCaml vs Rust: One-Level Flattening with flatten()
Side-by-Side Code
OCaml
(* Flatten list of lists — one level *)
let flat = List.concat [[1; 2]; [3; 4]; [5; 6]]
(* → [1; 2; 3; 4; 5; 6] *)
(* Filter out None, keep Some values *)
let values = List.filter_map Fun.id [Some 1; None; Some 3; None; Some 5]
(* → [1; 3; 5] *)
(* Characters from words via concat_map *)
let chars = List.concat_map
(fun w -> List.init (String.length w) (fun i -> w.[i]))
["hello"; "world"]
Rust (idiomatic — iterator flatten)
// Flatten Vec<Vec<T>> — one level removed
let flat: Vec<i32> = vec![vec![1, 2], vec![3, 4], vec![5, 6]]
.into_iter()
.flatten()
.collect();
// → [1, 2, 3, 4, 5, 6]
// Flatten Vec<Option<T>> — Nones discarded
let values: Vec<i32> = vec![Some(1), None, Some(3), None, Some(5)]
.into_iter()
.flatten()
.collect();
// → [1, 3, 5]
Rust (Option flattening — monadic join)
// flatten() on Option<Option<T>> — not just iterators
let a: Option<Option<i32>> = Some(Some(42));
assert_eq!(a.flatten(), Some(42));
let b: Option<Option<i32>> = Some(None);
assert_eq!(b.flatten(), None);
let c: Option<Option<i32>> = None;
assert_eq!(c.flatten(), None);
Type Signatures
| Concept | OCaml | Rust |
|---|---|---|
| Flatten list of lists | List.concat : 'a list list -> 'a list | Iterator::flatten → Flatten<I> |
| Filter-keep Somes | List.filter_map Fun.id | .into_iter().flatten() on Vec<Option<T>> |
| Collapse nested option | Option.join : 'a option option -> 'a option (≥ 4.08) | Option::flatten |
| Chars from strings | List.concat_map ... String.get | .flat_map(\|w\| w.chars()) |
Key Insights
flatten = monadic join**: In both languages, flattening one level of nesting is the categorical join operation (μ in monad theory). OCaml's List.concat and Rust's .flatten() are the same idea — remove exactly one constructor layer.Option is iterable in Rust**: Option<T> implements IntoIterator (yielding 0 or 1 items), so .flatten() on an iterator of Option<T> automatically discards None and unwraps Some. OCaml needs the dedicated List.filter_map Fun.id idiom instead.List.flatten applied recursively, .flatten() in Rust removes precisely one level. A Vec<Vec<Vec<T>>> becomes Vec<Vec<T>>, not Vec<T>. This mirrors OCaml's List.concat which also flattens only one level.flat_map(|x| x) vs flatten()**: In Rust, iter.flat_map(|x| x) and iter.flatten() are equivalent; flatten() is the preferred spelling because it removes the tautological closure and signals intent directly.When to Use Each Style
**Use .flatten() when:** you already have an iterator of iterables (e.g., after map() returns a Vec or Option) and want to concatenate them without an intermediate allocation or a redundant |x| x closure.
**Use .flat_map(f) when:* you need to transform and* flatten in one step — it is more composable and avoids an intermediate iterator adapter layer.
Exercises
flatten_result_values<T, E: Clone>(results: &[Result<Vec<T>, E>]) -> Result<Vec<T>, E> that concatenates all Ok vectors or returns the first Err.flatten_n_levels<T: Clone>(nested: Vec<Vec<Vec<T>>>) -> Vec<T> using two consecutive .flatten() calls..flatten() to implement optional_chain<A, B, C>(fa: Option<A>, f: impl Fn(A) -> Option<B>, g: impl Fn(B) -> Option<C>) -> Option<C>.