List.map — Transform Every Element
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
List.map f xs maps to Rust's iter().map(f).collect() pattern[head, tail @ ..])map always preserves length: the result has exactly as many elements as the input.iter().map(f).collect() as the canonical Rust form and map_recursive as the OCaml-style structural versionmap_indexed (.enumerate().map(|(i, x)| ...)) when the index is needed alongside the elementCode 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
List.map or explicit let rec[head, tail @ ..] is a direct analogue to OCaml's x :: xs — both destructure the head from the restVecs per call; the iterator version collects once. OCaml's linked-list cons cells make recursion O(n) with no extra allocation.List.map or explicit let rec[head, tail @ ..] is a direct analogue to OCaml's x :: xs — both destructure the head from the restVecs per call; the iterator version collects once. OCaml's linked-list cons cells make recursion O(n) with no extra allocation per step.List.map is not tail-recursive. Rust's iterator .map() is implemented as a lazy adapter — no recursion, no stack growth.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]);
}
}#[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
map_indexed that passes both the element and its index to the function: f: impl Fn(usize, &T) -> Uflat_map (OCaml's List.concat_map) that applies a function returning Vec<U> to each element and flattens the resultsfilter_map that applies a function returning Option<U> and collects only the Some valuesmap_indexed<T, U>(items: &[T], f: impl Fn(usize, &T) -> U) -> Vec<U> — equivalent to OCaml's List.mapi. Use .iter().enumerate().map(...).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.