Higher order iterator methods in Rust like map, filter, and fold let you transform, filter, and reduce sequences of values in a functional style. These methods work on any type that implements the Iterator trait, and they produce new iterators that are lazy and efficient.
After learning how to use .next() and build custom iterators, I found myself writing the same loop patterns repeatedly. That is when I discovered map, filter, and fold. These methods gave me a clean and powerful way to express transformations without writing verbose loops. They felt just like JavaScript array methods but faster and safer.
In this post, I will walk through how these higher-order methods work, show real examples, and explain how to chain them together to build expressive and efficient data pipelines.
map: Transform Each Item
The map method transforms each item in an iterator using a closure.
Here is an example:
let nums = vec![1, 2, 3];
let squares: Vec<i32> = nums.iter().map(|x| x * x).collect();
- map(|x| x * x) takes each number and returns its square
- collect() turns the result into a new Vec<i32>
You can also return different types:
let strings: Vec<String> = nums.iter().map(|x| format!("Num: {}", x)).collect();
Here, map returns a vector of strings.
filter: Keep Only Some Items
The filter method keeps only items that match a condition.
Example:
let nums = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<i32> = nums.into_iter().filter(|x| x % 2 == 0).collect();
This returns only the even numbers.
You can combine it with map:
let doubled_evens: Vec<i32> = (1..=10)
.filter(|x| x % 2 == 0)
.map(|x| x * 2)
.collect();
This creates a sequence of even numbers, doubles each one, and collects the result.
fold: Combine All Items Into One
The fold method reduces a sequence into a single value.
It takes:
- An initial value
- A closure with two arguments: the accumulator and the next item
Example:
let nums = vec![1, 2, 3, 4];
let sum = nums.iter().fold(0, |acc, x| acc + x);
- Starts with 0
- Adds each item to the accumulator
- Final result is 10
You can use fold to do much more than addition:
let product = (1..=4).fold(1, |acc, x| acc * x); // 24
let joined = ["a", "b", "c"].iter().fold(String::new(), |mut acc, &s| {
acc.push_str(s);
acc
});
Chaining Multiple Methods
You can chain map, filter, fold, and other iterator methods to build full pipelines:
let result: i32 = (1..=10)
.filter(|x| x % 2 != 0) // keep odd numbers
.map(|x| x * x) // square them
.fold(0, |acc, x| acc + x); // sum the squares
println!("{}", result); // 165
Each method returns a new iterator, which is lazy until you call collect(), fold(), or for_each().
Lazy Evaluation
Iterator methods do not do anything until they are consumed.
This means you can chain as many steps as you like without using memory until the final result is needed.
let result = (1..).filter(|x| x % 5 == 0).take(3);
for x in result {
println!("{}", x);
}
Here, only the first 3 numbers divisible by 5 are generated.
This is great for performance and memory safety.
Closures in Iterator Methods
You can pass closures with full access to the outer scope:
let factor = 3;
let scaled: Vec<i32> = (1..=5).map(|x| x * factor).collect();
We covered closure capture rules in the post Using Closures in Rust, and this example builds directly on that.
Working with collect()
The collect() method turns the final iterator into a concrete collection:
- Vec<T>
- HashSet<T>
- String (when mapping over chars)
- Any type that implements FromIterator
Make sure the compiler knows the target type, or use type annotations:
let squares: Vec<_> = (1..=3).map(|x| x * x).collect();
Summary
Higher-order iterator methods like map, filter, and fold let you write expressive and efficient code without manual loops. These methods transform, filter, and reduce values using closures. Since they are lazy, you can chain many of them without memory overhead, and only evaluate the final result when needed.