ExamplesBy LevelBy TopicLearning Paths
097 Intermediate

097 — Flatten and Flat Map

Functional Programming

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

  • • Use .flatten() on any iterator whose Item: IntoIterator
  • • Use .flat_map(f) as shorthand for .map(f).flatten()
  • • Flatten Vec<Option<T>> to filter out None values (since Option implements IntoIterator)
  • • Understand that both are lazy — no intermediate collections are allocated
  • • Map Rust's .flatten() to OCaml's List.concat and flat_map to List.concat_map
  • • Recognise flat_map as the monadic bind for the list/iterator monad
  • Code 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

    AspectRustOCaml
    Flatten.flatten()List.concat
    Flat map.flat_map(f)List.concat_map f
    Option filter.flatten() on Option<T> iterList.filter_map
    LazinessLazy (no intermediate collection)List.concat is eager
    GeneralityAny IntoIteratorLists only (without Seq)
    Monadic bindflat_map = bind for IteratorSame 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]);
        }
    }
    ✓ Tests Rust test suite
    #[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

  • • See example.ml for implementation
  • Rust Approach

  • • See example.rs for implementation
  • Comparison Table

    FeatureOCamlRust
    Seeexample.mlexample.rs

    Exercises

  • Use flat_map to split a sentence into individual words: sentences.iter().flat_map(|s| s.split_whitespace()).
  • Implement my_flatten<T>(v: Vec<Vec<T>>) -> Vec<T> without using .flatten() — use fold instead.
  • Use .flatten() on Vec<Result<T, E>> — note it only works with iter_ok-style logic; investigate why.
  • Write expand_range(ranges: &[(i32, i32)]) -> Vec<i32> that flattens each range into its elements.
  • In OCaml, implement flat_map_seq : ('a -> 'b Seq.t) -> 'a Seq.t -> 'b Seq.t for lazy flat mapping.
  • Open Source Repos