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 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
List.map f xs maps directly to Rust's iter().map(f).collect() iterator pipeline[head, tail @ ..][head, tail @ ..] is the precise analogue of OCaml's x :: xs cons-cell decompositioncollect) and the recursive form (one Vec per call frame)&[T] (a slice) rather than Vec<T>, preserving flexibility for callersCode 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
iter().map().collect(); OCaml uses List.map or let rec map; both abstract over the loop but via different mechanisms[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 cellVec at each call frame and extends it; OCaml's recursion allocates cons cells one at a time; the iterator version collects oncemap_recursive passes f as &impl Fn to borrow without consuming; OCaml closures are always heap-allocated references and can be passed freelyOCaml 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]);
}
}#[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 zero-based index to the transformation 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 results into a single Vec<U>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