ExamplesBy LevelBy TopicLearning Paths
881 Intermediate

881-iterator-adapters — Iterator Adapters

Functional Programming

Tutorial

The Problem

Functional programming's core power comes from composing small transformations into pipelines. Haskell's Data.List, OCaml's List module, and Python's itertools all provide map, filter, flat_map, take, and skip as standalone functions or methods. Rust packages these as lazy iterator adapters — each adapter wraps the previous iterator and transforms elements on demand, with no intermediate allocation. This pipeline model replaces nested loops with declarative data transformations. The lazy evaluation means even pipelines over infinite iterators terminate correctly when capped by .take() or .find().

🎯 Learning Outcomes

  • • Build data processing pipelines by chaining map, filter, flat_map, take, and skip
  • • Understand that iterator adapters are lazy — no work happens until consumed
  • • Use .chain() to concatenate iterators without allocating a combined container
  • • Write complex multi-step transformations as readable single expressions
  • • Compare with OCaml's List.map |> List.filter pipeline style
  • Code Example

    fn pipeline(data: &[i32]) -> Vec<String> {
        data.iter()
            .filter(|&&x| x > 0)
            .map(|&x| x * x)
            .map(|x| x.to_string())
            .collect()
    }

    Key Differences

  • Laziness: Rust adapters are lazy — no work until consumed; OCaml List operations are eager and create intermediate lists.
  • Allocation: Rust iterator pipelines avoid intermediate heap allocations; OCaml pipelines allocate a new list at each step.
  • Infinite sequences: Rust can chain adapters on infinite iterators safely (.take() bounds them); OCaml needs Seq for the same safety.
  • Syntax: Rust uses method chaining (.map().filter()); OCaml uses the pipe operator (|> List.map |> List.filter).
  • OCaml Approach

    OCaml uses chained function application with the pipe operator: list |> List.filter (fun x -> x > 0) |> List.map (fun x -> x * x). Each function returns a new list (eager evaluation), unlike Rust's lazy adapters. For lazy pipelines, OCaml uses Seq: Seq.of_list xs |> Seq.filter pred |> Seq.map f |> List.of_seq. The flat_map equivalent is List.concat_map. Take and skip: Seq.take, Seq.drop.

    Full Source

    #![allow(clippy::all)]
    // Example 087: Iterator Adapters
    // Chaining map/filter/flat_map/take/skip
    
    // === Approach 1: Basic map/filter pipeline ===
    fn pipeline(data: &[i32]) -> Vec<String> {
        data.iter()
            .filter(|&&x| x > 0)
            .map(|&x| x * x)
            .map(|x| x.to_string())
            .collect()
    }
    
    fn flat_map_example(data: &[&str]) -> Vec<String> {
        data.iter()
            .flat_map(|s| s.split_whitespace())
            .map(String::from)
            .collect()
    }
    
    // === Approach 2: Take/Skip/Chain ===
    fn take_skip_demo(data: &[i32]) -> Vec<i32> {
        data.iter()
            .copied()
            .filter(|x| x % 2 == 0)
            .map(|x| x * 3)
            .take(5)
            .collect()
    }
    
    fn skip_demo(data: &[i32], n: usize) -> Vec<i32> {
        data.iter().copied().skip(n).collect()
    }
    
    fn chain_demo(a: &[i32], b: &[i32]) -> Vec<i32> {
        a.iter().chain(b.iter()).copied().collect()
    }
    
    // === Approach 3: Complex chained pipelines ===
    fn word_lengths(text: &str) -> Vec<usize> {
        let mut lengths: Vec<usize> = text.split_whitespace().map(|w| w.len()).collect();
        lengths.sort();
        lengths
    }
    
    fn top_n<T, B: Ord>(data: &[T], n: usize, transform: impl Fn(&T) -> B) -> Vec<B> {
        let mut results: Vec<B> = data.iter().map(transform).collect();
        results.sort_by(|a, b| b.cmp(a));
        results.truncate(n);
        results
    }
    
    // Enumerate + filter pattern
    fn indexed_evens(data: &[i32]) -> Vec<(usize, i32)> {
        data.iter()
            .enumerate()
            .filter(|(_, &v)| v % 2 == 0)
            .map(|(i, &v)| (i, v))
            .collect()
    }
    
    // Inspect for debugging (side-effect adapter)
    fn debug_pipeline(data: &[i32]) -> Vec<i32> {
        data.iter()
            .copied()
            .inspect(|x| eprintln!("before filter: {}", x))
            .filter(|x| x % 2 == 0)
            .inspect(|x| eprintln!("after filter: {}", x))
            .map(|x| x * 10)
            .collect()
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_pipeline() {
            assert_eq!(pipeline(&[3, -1, 4, -5, 2]), vec!["9", "16", "4"]);
        }
    
        #[test]
        fn test_flat_map() {
            let result = flat_map_example(&["hello world", "foo bar"]);
            assert_eq!(result, vec!["hello", "world", "foo", "bar"]);
        }
    
        #[test]
        fn test_take_skip() {
            let data: Vec<i32> = (1..=14).collect();
            assert_eq!(take_skip_demo(&data), vec![6, 12, 18, 24, 30]);
        }
    
        #[test]
        fn test_skip() {
            assert_eq!(skip_demo(&[1, 2, 3, 4, 5], 3), vec![4, 5]);
        }
    
        #[test]
        fn test_chain() {
            assert_eq!(chain_demo(&[1, 2], &[3, 4]), vec![1, 2, 3, 4]);
        }
    
        #[test]
        fn test_word_lengths() {
            // "the"=3, "quick"=5, "brown"=5, "fox"=3 → sorted: [3, 3, 5, 5]
            assert_eq!(word_lengths("the quick brown fox"), vec![3, 3, 5, 5]);
        }
    
        #[test]
        fn test_top_n() {
            assert_eq!(top_n(&[1, 5, 3, 2, 4], 3, |x| x * x), vec![25, 16, 9]);
        }
    
        #[test]
        fn test_indexed_evens() {
            assert_eq!(
                indexed_evens(&[1, 2, 3, 4, 5, 6]),
                vec![(1, 2), (3, 4), (5, 6)]
            );
        }
    
        #[test]
        fn test_empty_pipeline() {
            assert_eq!(pipeline(&[]), Vec::<String>::new());
        }
    
        #[test]
        fn test_all_negative() {
            assert_eq!(pipeline(&[-1, -2, -3]), Vec::<String>::new());
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_pipeline() {
            assert_eq!(pipeline(&[3, -1, 4, -5, 2]), vec!["9", "16", "4"]);
        }
    
        #[test]
        fn test_flat_map() {
            let result = flat_map_example(&["hello world", "foo bar"]);
            assert_eq!(result, vec!["hello", "world", "foo", "bar"]);
        }
    
        #[test]
        fn test_take_skip() {
            let data: Vec<i32> = (1..=14).collect();
            assert_eq!(take_skip_demo(&data), vec![6, 12, 18, 24, 30]);
        }
    
        #[test]
        fn test_skip() {
            assert_eq!(skip_demo(&[1, 2, 3, 4, 5], 3), vec![4, 5]);
        }
    
        #[test]
        fn test_chain() {
            assert_eq!(chain_demo(&[1, 2], &[3, 4]), vec![1, 2, 3, 4]);
        }
    
        #[test]
        fn test_word_lengths() {
            // "the"=3, "quick"=5, "brown"=5, "fox"=3 → sorted: [3, 3, 5, 5]
            assert_eq!(word_lengths("the quick brown fox"), vec![3, 3, 5, 5]);
        }
    
        #[test]
        fn test_top_n() {
            assert_eq!(top_n(&[1, 5, 3, 2, 4], 3, |x| x * x), vec![25, 16, 9]);
        }
    
        #[test]
        fn test_indexed_evens() {
            assert_eq!(
                indexed_evens(&[1, 2, 3, 4, 5, 6]),
                vec![(1, 2), (3, 4), (5, 6)]
            );
        }
    
        #[test]
        fn test_empty_pipeline() {
            assert_eq!(pipeline(&[]), Vec::<String>::new());
        }
    
        #[test]
        fn test_all_negative() {
            assert_eq!(pipeline(&[-1, -2, -3]), Vec::<String>::new());
        }
    }

    Deep Comparison

    Comparison: Iterator Adapters

    Map/Filter Pipeline

    OCaml:

    let pipeline data =
      data
      |> List.filter (fun x -> x > 0)
      |> List.map (fun x -> x * x)
      |> List.map string_of_int
    

    Rust:

    fn pipeline(data: &[i32]) -> Vec<String> {
        data.iter()
            .filter(|&&x| x > 0)
            .map(|&x| x * x)
            .map(|x| x.to_string())
            .collect()
    }
    

    Flat Map

    OCaml:

    let flat_map_example data =
      data
      |> List.map (fun s -> String.split_on_char ' ' s)
      |> List.flatten
    

    Rust:

    fn flat_map_example(data: &[&str]) -> Vec<String> {
        data.iter()
            .flat_map(|s| s.split_whitespace())
            .map(String::from)
            .collect()
    }
    

    Take/Skip

    OCaml (Seq):

    let result =
      data |> List.to_seq
      |> Seq.filter (fun x -> x mod 2 = 0)
      |> Seq.map (fun x -> x * 3)
      |> Seq.take 5
      |> List.of_seq
    

    Rust:

    data.iter()
        .filter(|x| x % 2 == 0)
        .map(|x| x * 3)
        .take(5)
        .collect::<Vec<_>>()
    

    Exercises

  • Write a word_frequency pipeline that takes a &str, splits into words, lowercases, and counts occurrences using .fold() into a HashMap.
  • Implement moving_average using .windows(n) and .map() to compute overlapping window averages in a single iterator chain.
  • Write a cross_product function over two slices using .flat_map() and .map() that produces all (a, b) pairs.
  • Open Source Repos