ExamplesBy LevelBy TopicLearning Paths
911 Intermediate

911-iterator-flatten — Iterator Flatten

Functional Programming

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

  • • Use .flatten() to remove exactly one level of iterator nesting
  • • Understand that .flatten() is the monadic join and .flat_map() is monadic bind
  • • Apply .flatten() to Vec<Option<T>> using Option's IntoIterator implementation
  • • Use Option::flatten() to collapse Option<Option<T>> to Option<T>
  • • Compare with OCaml's List.concat and Option.join
  • Code 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 as iterable: Rust Option<T> implements IntoIterator (0 or 1 elements), enabling uniform .flatten() over Vec<Option<T>>; OCaml requires List.filter_map id.
  • One-level guarantee: Both .flatten() and List.concat remove exactly one level — no recursive deep-flattening.
  • join identity: Both express monadic join; Rust via the Iterator trait's implementation for iterable items; OCaml via the specific join/concat functions per type.
  • Laziness: Rust .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]]);
        }
    }
    ✓ Tests Rust test suite
    #[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

    ConceptOCamlRust
    Flatten list of listsList.concat : 'a list list -> 'a listIterator::flattenFlatten<I>
    Filter-keep SomesList.filter_map Fun.id.into_iter().flatten() on Vec<Option<T>>
    Collapse nested optionOption.join : 'a option option -> 'a option (≥ 4.08)Option::flatten
    Chars from stringsList.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.
  • One level only — intentionally: Unlike 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.
  • Zero allocation overhead: Both OCaml and Rust implementations process items lazily through the inner iterables without allocating an intermediate container — the outer list/iterator is consumed element by element.
  • 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

  • Write 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.
  • Implement flatten_n_levels<T: Clone>(nested: Vec<Vec<Vec<T>>>) -> Vec<T> using two consecutive .flatten() calls.
  • Use .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>.
  • Open Source Repos