When one value becomes many
You are processing a CSV file where each row contains a comma-separated list of tags. You need a single list of all tags across the entire file to build a frequency map. You write a loop that iterates over rows, splits each row, and pushes every tag into a result vector. It works, but the code is verbose. You reach for map, but map gives you a vector of vectors. You need to flatten that structure. You could chain map and flatten, but Rust offers a cleaner path. flat_map performs both steps in one pass.
Concept and analogy
flat_map is an iterator adapter that transforms each element into a collection and then flattens the result into a single stream. The name combines "map" and "flatten." You map each input to an output iterator, then flatten all those iterators into one continuous sequence.
Think of a mail sorting facility. You have boxes of letters arriving on a conveyor belt. Each box contains multiple envelopes. You want all envelopes in a single stream for processing. A map operation would hand you a stream of boxes. You would still need to open them. flat_map is the machine that opens each box immediately and passes the envelopes down the belt. You never see the boxes again. The output is just a stream of envelopes.
In Rust terms, flat_map takes a closure that returns something iterable. It calls that closure for every item in the source iterator, then stitches all the inner iterators together. The result yields items from the first inner iterator, then the second, and so on, until the source is exhausted.
Minimal example
fn main() {
// Start with a vector of vectors representing a matrix.
let matrix = vec![vec![1, 2], vec![3, 4], vec![5, 6]];
// flat_map applies the closure to each inner vector.
// The closure returns an iterator over the row's elements.
// flat_map stitches these iterators into one stream.
let flattened: Vec<i32> = matrix
.iter()
.flat_map(|row| row.iter())
.collect();
println!("{:?}", flattened); // [1, 2, 3, 4, 5, 6]
}
The closure |row| row.iter() returns an iterator. flat_map consumes the outer iterator and yields items from the inner iterators sequentially. The type annotation on flattened helps the compiler infer the collection type for collect.
How the machinery works
Iterators in Rust are lazy. Calling flat_map does not process any data. It returns a new iterator that wraps the original one and stores the closure. Processing begins only when you drive the iterator, usually by calling collect or looping with for.
When you request the next item, flat_map checks if the current inner iterator has items left. If it does, it yields the next item from that inner iterator. If the inner iterator is exhausted, flat_map asks the outer iterator for the next item, runs the closure to create a new inner iterator, and repeats. This fusion means no intermediate collections are allocated. You get the logical effect of mapping and flattening with the performance of a single loop.
The closure return type is constrained by the IntoIterator trait. This trait allows the closure to return a Vec, a slice, an array, or an iterator. flat_map converts the return value into an iterator automatically. This flexibility lets you write convenient code without manual iterator construction.
fn main() {
let numbers = vec![1, 2, 3];
// The closure returns an array literal.
// IntoIterator converts [i32; 2] into an iterator.
// No allocation occurs.
let doubled_and_next: Vec<i32> = numbers
.iter()
.flat_map(|n| [n * 2, n * 2 + 1])
.collect();
println!("{:?}", doubled_and_next); // [2, 3, 4, 5, 6, 7]
}
Returning an array is zero-cost. Returning a Vec inside the closure allocates memory for every element. Prefer arrays or slices when the size is known at compile time.
Realistic scenario: aggregating user data
You are building an API response that aggregates friend lists. Each user has a list of friend IDs. You need a flat list of all friend IDs to check for duplicates or count connections.
struct User {
name: String,
friend_ids: Vec<u32>,
}
fn get_all_friend_ids(users: &[User]) -> Vec<u32> {
// Map each user to an iterator over their friend IDs.
// flat_map flattens the result into a single stream.
// copied() converts &u32 to u32 for Copy types.
users
.iter()
.flat_map(|user| user.friend_ids.iter())
.copied()
.collect()
}
fn main() {
let users = vec![
User { name: "Alice".to_string(), friend_ids: vec![1, 2] },
User { name: "Bob".to_string(), friend_ids: vec![2, 3] },
];
let ids = get_all_friend_ids(&users);
println!("{:?}", ids); // [1, 2, 2, 3]
}
Notice the .copied() call. The closure returns &u32 because user.friend_ids.iter() yields references. collect wants owned u32 values. copied() dereferences types that implement Copy. It is the community convention for numeric types and other Copy data. Use cloned() for types like String where you need to duplicate heap-allocated data. copied() signals to readers that no allocation is happening.
Option and Result conventions
Option<T> and Result<T, E> also have flat_map methods. They work similarly: if the value is Some or Ok, the closure runs and returns a new Option or Result. The outer wrapper is flattened away.
fn parse_id(input: &str) -> Option<u32> {
input.parse().ok()
}
fn process(maybe_id: Option<u32>) -> Option<String> {
// flat_map on Option flattens Option<Option<T>> to Option<T>.
maybe_id
.flat_map(|id| if id > 10 { Some(format!("Valid: {}", id)) } else { None })
}
The community convention for Option and Result is to use and_then instead of flat_map. and_then reads better as control flow. It emphasizes chaining operations that might fail. flat_map is reserved for iterators where the focus is data transformation. Use and_then for Option and Result. Use flat_map for iterators.
Pitfalls and compiler errors
Type inference can struggle with flat_map if the return type is ambiguous. The compiler needs to know what type the closure returns and what type collect produces. If you omit type annotations, you might see E0282 (type annotations needed). Add a type annotation to the variable or the collect call to resolve ambiguity.
// This may fail to compile without a type hint.
// let result = vec![1, 2].iter().flat_map(|x| [x, x+1]).collect();
// Error[E0282]: type annotations needed
// Fix: annotate the result type.
let result: Vec<i32> = vec![1, 2].iter().flat_map(|x| [x, x+1]).collect();
If your closure returns a value that does not implement IntoIterator, the compiler rejects the code with E0277 (the trait bound IntoIterator is not satisfied). This happens when you accidentally return a single value instead of a collection.
// This fails. The closure returns i32, not an iterator.
// let result = vec![1, 2].iter().flat_map(|x| x * 2).collect();
// Error[E0277]: the trait bound `{integer}: IntoIterator` is not satisfied
Use map when you want to transform each element into a single value. flat_map expects the closure to return something iterable.
Allocation inside the closure is a common performance trap. If you write flat_map(|x| vec![x, x+1]), you allocate a new vector for every input item. This defeats the purpose of iterator fusion. Return an array, a slice, or an existing iterator to avoid allocation.
Decision matrix
Use flat_map when you need to transform each item into a sequence and merge all sequences into one stream. Use map when your transformation produces exactly one output value per input. Use flatten when you have nested iterators and want to remove one level of nesting without changing the data. Use filter_map when you want to transform items and optionally skip them by returning None. Use and_then when chaining Option or Result computations.