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 and same order. This is the foundational higher-order function in functional programming: map separates the "what to do to each element" from the "how to iterate," making it the first pattern to master in any functional language. The example provides two Rust implementations — an idiomatic iterator version and a recursive structural version — so you can see both styles side by side.

🎯 Learning Outcomes

  • • How OCaml's List.map f xs maps directly to Rust's iter().map(f).collect() iterator pipeline
  • • How to express structural recursion over a slice using Rust's slice pattern syntax [head, tail @ ..]
  • • Why [head, tail @ ..] is the precise analogue of OCaml's x :: xs cons-cell decomposition
  • • The performance trade-off between the iterator form (single allocation at collect) and the recursive form (one Vec per call frame)
  • • Why the idiomatic Rust form takes &[T] (a slice) rather than Vec<T>, preserving flexibility for callers
  • 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

  • Canonical idiom: Rust's standard form uses iter().map().collect(); OCaml uses List.map or let rec map; both abstract over the loop but via different mechanisms
  • Pattern syntax: Rust's [head, tail @ ..] and OCaml's x :: xs are structurally equivalent decompositions, but Rust works on contiguous slice memory while OCaml destructs a heap-allocated cons cell
  • Allocation model: The recursive Rust version allocates a new Vec at each call frame and extends it; OCaml's recursion allocates cons cells one at a time; the iterator version collects once
  • Closure passing: map_recursive passes f as &impl Fn to borrow without consuming; OCaml closures are always heap-allocated references and can be passed freely
  • OCaml Approach

    OCaml ships List.map as part of the standard library: List.map (fun x -> x * 2) [1;2;3;4;5] gives [2;4;6;8;10]. The standard recursive definition is let rec map f = function | [] -> [] | x :: xs -> f x :: map f xs. Both forms are idiomatic — library use for production code, explicit recursion for teaching. OCaml's linked-list representation makes pattern matching on x :: xs natural: the cons cell structurally separates head from tail with no overhead.

    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 zero-based index to the transformation 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 into a single Vec<U>
  • Implement filter_map that applies a function returning Option<U> and collects only the Some values, then verify it produces the same result as chaining .filter().map() on the iterator
  • Open Source Repos