Nugget 13
Home
map() gives you one output per input. But what if one word should
produce multiple letters? One person produce multiple tags? That's when you
need flat_map.
Sometimes each input item produces multiple outputs. Using
map gives you an iterator of iterators:
let words = ["hello", "world"];
// map: each word → its chars iterator → Vec of Vecs
let letters: Vec<Vec<char>> = words.iter()
.map(|word| word.chars().collect::<Vec<_>>())
.collect();
// [['h', 'e', 'l', 'l', 'o'], ['w', 'o', 'r', 'l', 'd']]
// But what if you just want a flat list of all characters?
// ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
You could collect and then flatten in two steps. Or use flat_map
in one step.
flat_map() — Map Then Flatten
flat_map() is like map() but the closure returns an
iterator, and the results are flattened into a single sequence:
let words = ["hello", "world"];
// flat_map: map each word to chars, then flatten everything
let letters: Vec<char> = words.iter()
.flat_map(|word| word.chars())
.collect();
// ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
// Another example: expand numbers into ranges
let expanded: Vec<i32> = [1, 3, 2].iter()
.flat_map(|&n| 1..=n)
.collect();
// 1..=1 → [1], 1..=3 → [1,2,3], 1..=2 → [1,2]
// Result: [1, 1, 2, 3, 1, 2]
The closure returns an iterator (not a collection), so nothing is allocated in between — each inner iterator produces items one at a time.
A common pattern: split text into lines, then each line into words:
let poem = "the quick brown\nfox jumps over\nthe lazy dog";
// Each line → words, then collect everything flat
let all_words: Vec<&str> = poem
.lines()
.flat_map(|line| line.split_whitespace())
.collect();
println!("{:?}", all_words);
// ["the", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
Without flat_map you'd need nested loops. With it, you get a
flat sequence in one pipeline.
flatten() — Just Flatten
flatten() is flat_map without the mapping step —
it just removes one level of nesting:
let nested = vec![
vec![1, 2, 3],
vec![4, 5],
vec![6, 7, 8, 9],
];
let flat: Vec<i32> = nested.iter()
.flatten() // each &Vec<i32> → &i32 items
.copied() // &i32 → i32
.collect();
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
// flatten works on any nested iterator
flatten() with Option
One of the most useful applications: Option implements
IntoIterator (0 or 1 items), so flatten() on an
iterator of Options drops all Nones:
let items = vec![Some(1), None, Some(2), None, Some(3)];
let just_values: Vec<i32> = items.into_iter()
.flatten() // None → yields nothing, Some(x) → yields x
.collect();
// [1, 2, 3]
// Equivalent to:
let just_values: Vec<i32> = items.into_iter()
.filter_map(|x| x)
.collect();
// filter_map is for when you need custom filtering + mapping
💡 filter_map for custom logic
Use filter_map() when you want to both filter and transform at
once — return Some(result) for keep or None to skip:
.filter_map(|x| x.parse().ok())
Like all iterator adapters, flat_map() and flatten()
are lazy. The inner iterators don't allocate — each item is produced on demand:
// Lazy: inner iterators are never fully materialized
let words = ["hello", "world"];
let mut flat = words.iter().flat_map(|w| w.chars());
assert_eq!(flat.next(), Some('h'));
assert_eq!(flat.next(), Some('e'));
// Only 2 chars produced so far — "world" hasn't started yet
// JS equivalent creates TWO intermediate arrays
// "hello".split('') → ['h','e','l','l','o']
// "world".split('') → ['w','o','r','l','d']
// then flattens → one array of 10 chars
📐 Zero-allocation flattening
In JavaScript, arr.flatMap(fn) is also lazy-ish in some engines,
but the common pattern of .map().flat() creates intermediate
arrays. Rust's flat_map is always a single pass with no
intermediate allocations.
flat_map() = map() + flatten() — one-to-many transformations.flat_map when each input produces multiple outputs (words → chars, lines → tokens).flatten() removes one level of nesting — works with nested iterators or Option/Result items.filter_map() is flat_map's cousin — filter + transform in one step.