ExamplesBy LevelBy TopicLearning Paths
025 Intermediate

List.map — Transform Every Element

Functional Programming

Tutorial

The Problem

Apply a transformation function to every element of a list, producing a new list of the same length. Implement both the idiomatic Rust iterator approach and a recursive structural version that mirrors OCaml's List.map.

List.map is the first function taught in every functional programming course — it abstracts the universal pattern of "do this to every element". Understanding map deeply means understanding: (1) it preserves the length of the input, (2) it applies its function to elements independently (enabling parallelism), (3) it is the functor law (identity maps to identity, composition distributes). These properties make map the safe, composable workhorse of data transformation pipelines in Spark, React, NumPy, and every functional language's standard library.

🎯 Learning Outcomes

  • • How OCaml's List.map f xs maps to Rust's iter().map(f).collect() pattern
  • • Structural recursion on slices using Rust's slice pattern matching ([head, tail @ ..])
  • • The difference between idiomatic Rust (iterator chains) and OCaml-style explicit recursion
  • • Understand that map always preserves length: the result has exactly as many elements as the input
  • • Recognize .iter().map(f).collect() as the canonical Rust form and map_recursive as the OCaml-style structural version
  • • Use map_indexed (.enumerate().map(|(i, x)| ...)) when the index is needed alongside the element
  • Code Example

    #![allow(clippy::all)]
    // 025: List.map — Transform Every Element
    // OCaml: List.map (fun x -> x * 2) numbers
    // Rust:  iter().map(...).collect()
    
    // Solution 1: Idiomatic Rust — how a Rust developer writes it
    // Takes &[T], applies f to each element, collects into Vec<U>
    pub fn map_transform<T, U>(items: &[T], f: impl Fn(&T) -> U) -> Vec<U> {
        items.iter().map(f).collect()
    }
    
    // Solution 2: Functional/recursive — closer to OCaml style
    // Mirrors OCaml's structural recursion over cons cells
    pub fn map_recursive<T, U>(items: &[T], f: &impl Fn(&T) -> U) -> Vec<U> {
        match items {
            [] => vec![],
            [head, tail @ ..] => {
                let mut result = vec![f(head)];
                result.extend(map_recursive(tail, f));
                result
            }
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_empty() {
            let result: Vec<i32> = map_transform(&[], |x| x * 2);
            assert_eq!(result, Vec::<i32>::new());
        }
    
        #[test]
        fn test_double_elements() {
            assert_eq!(
                map_transform(&[1, 2, 3, 4, 5], |x| x * 2),
                vec![2, 4, 6, 8, 10]
            );
        }
    
        #[test]
        fn test_single_element() {
            assert_eq!(map_transform(&[7], |x| x + 1), vec![8]);
        }
    
        #[test]
        fn test_string_transform() {
            let words = vec!["hello", "world"];
            assert_eq!(map_transform(&words, |s| s.len()), vec![5, 5]);
        }
    
        #[test]
        fn test_recursive_empty() {
            let result: Vec<i32> = map_recursive(&[], &|x| x * 2);
            assert_eq!(result, Vec::<i32>::new());
        }
    
        #[test]
        fn test_recursive_double() {
            assert_eq!(
                map_recursive(&[1, 2, 3, 4, 5], &|x| x * 2),
                vec![2, 4, 6, 8, 10]
            );
        }
    
        #[test]
        fn test_recursive_single() {
            assert_eq!(map_recursive(&[42], &|x| x * 3), vec![126]);
        }
    
        #[test]
        fn test_recursive_square() {
            assert_eq!(map_recursive(&[1, 2, 3, 4], &|x| x * x), vec![1, 4, 9, 16]);
        }
    }

    Key Differences

  • Idiom: Rust's canonical form uses iterator adapters; OCaml uses either List.map or explicit let rec
  • Slice patterns: Rust's [head, tail @ ..] is a direct analogue to OCaml's x :: xs — both destructure the head from the rest
  • Performance: The recursive Rust version allocates intermediate Vecs per call; the iterator version collects once. OCaml's linked-list cons cells make recursion O(n) with no extra allocation.
  • Idiom: Rust's canonical form uses iterator adapters; OCaml uses either List.map or explicit let rec
  • Slice patterns: Rust's [head, tail @ ..] is a direct analogue to OCaml's x :: xs — both destructure the head from the rest
  • Performance: The recursive Rust version allocates intermediate Vecs per call; the iterator version collects once. OCaml's linked-list cons cells make recursion O(n) with no extra allocation per step.
  • Tail recursion: OCaml's List.map is not tail-recursive. Rust's iterator .map() is implemented as a lazy adapter — no recursion, no stack growth.
  • Order preservation: Both List.map in OCaml and .iter().map() in Rust preserve the original element order — they process elements sequentially from first to last.
  • OCaml Approach

    List.map (fun x -> x * 2) numbers is a standard library function. The recursive definition is let rec map f = function | [] -> [] | x :: xs -> f x :: map f xs. Both forms are idiomatic in OCaml.

    List.map (fun x -> x * 2) numbers is a standard library function. The recursive definition is let rec map f = function | [] -> [] | x :: xs -> f x :: map f xs. Both forms are idiomatic in OCaml. The function keyword is shorthand for fun x -> match x with, making the recursive definition concise. OCaml's List.map is not tail-recursive for large lists — use List.rev_map (reversed) followed by List.rev for large inputs.

    Full Source

    #![allow(clippy::all)]
    // 025: List.map — Transform Every Element
    // OCaml: List.map (fun x -> x * 2) numbers
    // Rust:  iter().map(...).collect()
    
    // Solution 1: Idiomatic Rust — how a Rust developer writes it
    // Takes &[T], applies f to each element, collects into Vec<U>
    pub fn map_transform<T, U>(items: &[T], f: impl Fn(&T) -> U) -> Vec<U> {
        items.iter().map(f).collect()
    }
    
    // Solution 2: Functional/recursive — closer to OCaml style
    // Mirrors OCaml's structural recursion over cons cells
    pub fn map_recursive<T, U>(items: &[T], f: &impl Fn(&T) -> U) -> Vec<U> {
        match items {
            [] => vec![],
            [head, tail @ ..] => {
                let mut result = vec![f(head)];
                result.extend(map_recursive(tail, f));
                result
            }
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_empty() {
            let result: Vec<i32> = map_transform(&[], |x| x * 2);
            assert_eq!(result, Vec::<i32>::new());
        }
    
        #[test]
        fn test_double_elements() {
            assert_eq!(
                map_transform(&[1, 2, 3, 4, 5], |x| x * 2),
                vec![2, 4, 6, 8, 10]
            );
        }
    
        #[test]
        fn test_single_element() {
            assert_eq!(map_transform(&[7], |x| x + 1), vec![8]);
        }
    
        #[test]
        fn test_string_transform() {
            let words = vec!["hello", "world"];
            assert_eq!(map_transform(&words, |s| s.len()), vec![5, 5]);
        }
    
        #[test]
        fn test_recursive_empty() {
            let result: Vec<i32> = map_recursive(&[], &|x| x * 2);
            assert_eq!(result, Vec::<i32>::new());
        }
    
        #[test]
        fn test_recursive_double() {
            assert_eq!(
                map_recursive(&[1, 2, 3, 4, 5], &|x| x * 2),
                vec![2, 4, 6, 8, 10]
            );
        }
    
        #[test]
        fn test_recursive_single() {
            assert_eq!(map_recursive(&[42], &|x| x * 3), vec![126]);
        }
    
        #[test]
        fn test_recursive_square() {
            assert_eq!(map_recursive(&[1, 2, 3, 4], &|x| x * x), vec![1, 4, 9, 16]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_empty() {
            let result: Vec<i32> = map_transform(&[], |x| x * 2);
            assert_eq!(result, Vec::<i32>::new());
        }
    
        #[test]
        fn test_double_elements() {
            assert_eq!(
                map_transform(&[1, 2, 3, 4, 5], |x| x * 2),
                vec![2, 4, 6, 8, 10]
            );
        }
    
        #[test]
        fn test_single_element() {
            assert_eq!(map_transform(&[7], |x| x + 1), vec![8]);
        }
    
        #[test]
        fn test_string_transform() {
            let words = vec!["hello", "world"];
            assert_eq!(map_transform(&words, |s| s.len()), vec![5, 5]);
        }
    
        #[test]
        fn test_recursive_empty() {
            let result: Vec<i32> = map_recursive(&[], &|x| x * 2);
            assert_eq!(result, Vec::<i32>::new());
        }
    
        #[test]
        fn test_recursive_double() {
            assert_eq!(
                map_recursive(&[1, 2, 3, 4, 5], &|x| x * 2),
                vec![2, 4, 6, 8, 10]
            );
        }
    
        #[test]
        fn test_recursive_single() {
            assert_eq!(map_recursive(&[42], &|x| x * 3), vec![126]);
        }
    
        #[test]
        fn test_recursive_square() {
            assert_eq!(map_recursive(&[1, 2, 3, 4], &|x| x * x), vec![1, 4, 9, 16]);
        }
    }

    Exercises

  • Implement map_indexed that passes both the element and its index to the function: f: impl Fn(usize, &T) -> U
  • Implement flat_map (OCaml's List.concat_map) that applies a function returning Vec<U> to each element and flattens the results
  • Implement filter_map that applies a function returning Option<U> and collects only the Some values
  • Map with index: Implement map_indexed<T, U>(items: &[T], f: impl Fn(usize, &T) -> U) -> Vec<U> — equivalent to OCaml's List.mapi. Use .iter().enumerate().map(...).
  • Parallel map: Replace iter() with par_iter() from the rayon crate to implement parallel map. The interface is identical — only the import changes. Explain when this is faster than sequential map.
  • Open Source Repos