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.
Rust Intermediate Concepts
- Generic Types in Functions and Structs
- Implementing Traits for Custom Behavior in Rust
- Trait Bounds and Constraints in Rust
- Lifetimes in Rust
- Using Closures (Anonymous Functions) in Rust
- The Iterator Trait and .next() in Rust
- Higher Order Iterator Methods: map, filter, and fold in Rust
- Using impl Trait for Simplicity in Rust
- Advanced Collections: HashSet and BTreeMap in Rust
- Custom Error Types and the Error Trait in Rust
- Option and Result Combinators in Rust
- Writing Unit Tests in Rust
- Integration Testing in Rust
- Logging in Rust with the log Crate
- Cargo Tips and Tricks for Rust Projects
- CLI Argument Parsing with Clap in Rust
- File I/O and File System Operations in Rust
- Running External Commands in Rust
- Make HTTP Requests in Rust with Reqwest
- JSON Serialization and Deserialization in Rust with Serde
- Building a Weather CLI in Rust
- Date and Time Handling in Rust with Chrono
- Using Regular Expressions in Rust with the regex Crate
- Memory Management in Rust
- Understanding Borrow Checker Errors in Rust
- Interacting with Databases in Rust
- Building a Todo List CLI in Rust with SQLite
- Concurrency in Rust