[Rust Series Part 15] Shaping Data Flow with Iterators and Closures

한국어 버전

Loops over collections grow wordy fast. Rust's iterators and closures let you express the same logic in shorter, more declarative pipelines. Here we tour the key Iterator methods, closure syntax, and how they pair together in practice.

Start with Closure Syntax

Closures are anonymous functions that can capture surrounding scope.

fn main() {
    let threshold = 50;
    let is_high = |score: i32| score > threshold;

    println!("Is 70 passing? {}", is_high(70));
}
  • |score: i32| score > threshold is the closure.
  • You can omit types and let the compiler infer them.
  • Capture mode—by move, immutable borrow, or mutable borrow—depends on how the body uses the captured values. Add move explicitly when you need to transfer ownership.

A quick heuristic:

  • Read-only use captures by shared reference.
  • Mutating the capture uses a mutable reference.
  • Passing data out of the closure often requires move.

Core Ideas Behind the Iterator Trait

  • Every iterator implements a next method.
  • Standard collections (Vec, HashMap, Range, …) expose iter, iter_mut, and into_iter constructors.
  • Chainable adapters such as map, filter, take, and collect let you build pipelines.
fn main() {
    let scores = vec![45, 67, 88, 52];
    let high_scores: Vec<_> = scores
        .iter()
        .filter(|score| **score >= 60)
        .map(|score| format!("Pass: {}", score))
        .collect();

    println!("{:?}", high_scores);
}
  • iter() yields immutable references, into_iter() consumes the collection.
  • collect() gathers results into the desired collection type. When inference fails, annotate the target type (let result: Vec<_> = ...).

Ownership Rules and Iterators

  • iter() yields &T values, iter_mut() yields &mut T, into_iter() yields owned T.
  • Mutating through iter_mut() updates the original collection immediately.
fn main() {
    let mut names = vec!["yuna".to_string(), "min".to_string()];
    names.iter_mut().for_each(|name| name.make_ascii_uppercase());
    println!("{:?}", names);
}

Building Custom Iterators

struct Counter {
    current: u32,
    max: u32,
}

impl Counter {
    fn new(max: u32) -> Self {
        Self { current: 0, max }
    }
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.current >= self.max {
            None
        } else {
            self.current += 1;
            Some(self.current)
        }
    }
}

fn main() {
    let sum: u32 = Counter::new(5).map(|n| n * 2).sum();
    println!("sum = {}", sum);
}
  • type Item specifies what the iterator yields.
  • Consumers like sum or product exhaust the iterator to produce a single value.

Closures Plus Error Handling

  • Methods such as filter_map or map_while express pipelines that mix Result and Option cleanly.
fn parse_numbers(lines: &[&str]) -> Result<Vec<i32>, std::num::ParseIntError> {
    lines
        .iter()
        .map(|line| line.trim().parse::<i32>())
        .collect()
}
  • collect() can infer Result<Vec<_>, E>. It stops early on the first Err and otherwise gathers all values.

Practice in CodeSandbox

The sandbox below uses CodeSandbox's Rust starter. Move the main code into src/main.rs, then compare cargo check and cargo run so you can read the compiler feedback beside the final output.

Live Practice

Rust Practice Sandbox

CodeSandbox

Run the starter project in CodeSandbox, compare it with the lesson code, and keep experimenting.

Rust startercargoterminal
  1. Fork the starter and open src/main.rs
  2. Paste the lesson code and run cargo check plus cargo run in order
  3. Change types, values, or borrowing flow and compare the compiler feedback with the output

Rust practice here is mainly terminal-driven rather than browser-preview driven. Lessons that need multiple files or extra crates may require a bit more setup inside the starter.

Practice Exercises

  1. Start with Vec<User> and build a pipeline that filters active users and collects their names.
  2. Compare into_iter() and iter() by moving ownership out of a vector and then trying to print the original.
  3. Implement Iterator for a Fibonacci generator and use take(10).collect::<Vec<_>>() to capture the first values.

Completion Checklist

  • Comfortable with closure syntax and capture rules.
  • Have used iterator combinators such as map, filter, collect, and sum.
  • Have written a custom iterator that implements next.

By the end of part 15 you've tied together collections, error handling, modules, generics, lifetimes, and iterators. In part 16 we'll dive into smart pointers and heap-managed data to deepen your model of Rust's memory story.

💬 댓글

이 글에 대한 의견을 남겨주세요