Rust ClosuresFunctional ProgrammingIteratorsRust

Article & Tutorial

Rust Iterators and Closures: map, filter, collect and Functional Patterns

Learn Rust closures and iterators, iterator adapters, map, filter, collect, ownership captures and practical zero-cost data pipelines.

Rust Iterators and Closures: map, filter, collect and Functional Patterns article illustration
Rust Iterators and Closures: map, filter, collect and Functional Patterns article illustration
Contents

Rust Learning Path — Chapter 18. Closures and iterators let you express transformations clearly while still compiling to efficient native code. They are everywhere in idiomatic Rust—from collection processing to async streams and application configuration.

1. Closures are anonymous functions

bash
fn main() { let add = |a: i32, b: i32| a + b; println!("{}", add(2, 3)); }

Closure parameter and return types can often be inferred from usage.

2. Closures can capture their environment

bash
fn main() { let prefix = String::from("user-"); let format_id = |id: u64| format!("{prefix}{id}"); println!("{}", format_id(42)); }

Unlike a normal fn, a closure can borrow or take ownership of surrounding values.

3. Fn, FnMut and FnOnce

TraitTypical behavior
FnCan be called repeatedly without mutating captured state.
FnMutMay mutate captured state.
FnOnceMay consume captured values and therefore might be callable only once.
bash
fn run_twice<F>(mut action: F) where F: FnMut(), { action(); action(); }

4. move closures

move forces a closure to take ownership of captured values, which is especially important when sending work to another thread:

bash
use std::thread; fn main() { let message = String::from("hello"); let handle = thread::spawn(move || { println!("{message}"); }); handle.join().unwrap(); }

5. Iterators are lazy

Calling iter() does not immediately process every element. Iterator adapters build a pipeline, and a consuming adapter such as collect, sum or a for loop drives it.

bash
let values = vec![1, 2, 3, 4]; let doubled = values.iter().map(|n| n * 2);

6. iter, iter_mut and into_iter

MethodProducesOwnership
iter()ReferencesCollection remains usable
iter_mut()Mutable referencesCollection remains owned
into_iter()Owned itemsUsually consumes the collection

7. map

bash
let ports = vec![80, 443, 8080]; let labels: Vec<String> = ports .iter() .map(|port| format!("tcp:{port}")) .collect();

8. filter

bash
let numbers = vec![1, 2, 3, 4, 5, 6]; let evens: Vec<_> = numbers .into_iter() .filter(|n| n % 2 == 0) .collect();

9. filter_map

filter_map is useful when each input may or may not produce an output:

bash
let raw = vec!["10", "bad", "20"]; let parsed: Vec<i32> = raw .into_iter() .filter_map(|s| s.parse().ok()) .collect();

10. find, any and all

bash
let ports = [22, 80, 443]; assert!(ports.iter().any(|p| *p == 443)); assert!(ports.iter().all(|p| *p > 0)); let http = ports.iter().find(|p| **p == 80);

11. fold

fold combines values into an accumulator:

bash
let total = [10, 20, 30] .into_iter() .fold(0, |acc, n| acc + n);

12. enumerate and zip

bash
for (index, value) in ["api", "db", "cache"].iter().enumerate() { println!("{index}: {value}"); } let names = ["api", "worker"]; let ports = [8080, 9090]; for (name, port) in names.iter().zip(ports) { println!("{name} -> {port}"); }

13. A practical data pipeline

bash
#[derive(Debug)] struct Server { name: String, cpu: u8, } fn main() { let servers = vec![ Server { name: "api-1".into(), cpu: 40 }, Server { name: "api-2".into(), cpu: 92 }, Server { name: "db-1".into(), cpu: 75 }, ]; let overloaded: Vec<String> = servers .into_iter() .filter(|s| s.cpu >= 80) .map(|s| s.name) .collect(); println!("{overloaded:?}"); }

14. Avoid unnecessary allocations

You do not always need collect(). If the next operation can consume the iterator directly, keep it lazy:

bash
let total: i32 = (1..=100) .filter(|n| n % 2 == 0) .sum();

15. Common mistakes

  • Using into_iter() and then trying to reuse a moved collection.
  • Collecting after every adapter instead of keeping one pipeline.
  • Writing a complex chain that is harder to read than a simple loop.
  • Cloning inside closures when borrowing would work.

Mini lab

Build a log analyzer that accepts a vector of status-code strings, parses valid values, keeps only errors >= 400, groups or counts them, and prints a summary. Use filter_map, filter, and collect.

Key takeaways

Closures package behavior and can capture surrounding values. Iterators provide composable, lazy data processing. The important skill is choosing ownership correctly and keeping pipelines readable.

Did this help?

Yes No

Join the discussion

0 Comments

Ask questions, add context, or share what worked.

Sign in to join the discussion

No comments yet. Start the discussion.

More from Melochip Editorial

Related articles

Stay in the loop. Get new Melochip articles

Receive Melochip article updates when new articles are available. Explicit opt-in only.

No pre-checked boxes. Unsubscribe support is part of the subscriber foundation.