Skip to Content
Course content

58: Iterators and Iterator Adapters in Rust

Click on the "Edit" button in the top corner of the screen to edit your slide content.

One of the most powerful parts of Rust is how it handles sequences of data. If you're coming from a C background, you're probably used to for loops with indices. If you're from Python or JavaScript, you know map and filter. Rust's iterators are a bit of a hybrid—they give you that high-level functional feel, but they compile down to machine code that's often just as fast as a manual loop. This is what we call "zero-cost abstractions."

To see this in action, let's build a simple log parser. Imagine we have a list of log entries, and we only care about the unique error codes from the "ERROR" level messages. We want to ignore "INFO" and "WARN" entirely.

Starting with a raw sequence

First, let's get some dummy data. I'll use a slice of strings here. In a real app, this might be reading from a file, but for this example, we'll keep it in memory.


let logs = vec![
    "INFO: System boot complete",
    "ERROR: 404 Not Found",
    "WARN: High memory usage",
    "ERROR: 500 Internal Server Error",
    "ERROR: 404 Not Found",
    "INFO: User logged in",
    "ERROR: 403 Forbidden",
];

Now, I could write a for loop and push results into a new Vec, but that's a lot of boilerplate. Instead, I'll start an iterator chain using .iter().

Filtering out the noise

I only want the errors. The filter adapter is perfect here. It takes a closure that returns a boolean; if it's true, the element stays in the stream.


let errors = logs.iter()
    .filter(|line| line.starts_with("ERROR"));

At this point, errors isn't actually a list of strings. It's an Iterator object. It's just a set of instructions waiting to be executed. This is a key concept in Rust: iterator adapters are lazy.

Extracting the error codes

I don't need the whole "ERROR: " prefix; I just want the code (like "404"). I'll use map to transform each string. I'll split the string by the colon and take the second part, trimming the whitespace.


let codes = logs.iter()
    .filter(|line| line.starts_with("ERROR"))
    .map(|line| line.split(':').nth(1).unwrap_or("Unknown").trim());

The "Where is my data?" moment

Now, here is where I usually trip up when I'm rushing through a project. I'll try to print codes or use it in a loop, and I'll realize... nothing is happening. Or, more likely, I'll try to pass it to a function that expects a Vec and get a compiler error saying I'm passing a Map struct instead.

I forgot that we've only defined how to process the data. We haven't actually told Rust to do it. To trigger the iteration, we need a consuming adapter (or a "terminal operation").

Collecting and counting results

To actually get the data into a collection, I'll use collect(). This is a bit of a magic method because it can turn an iterator into many different things (Vecs, HashSets, etc.), so I usually have to give it a type hint.

Since I only want unique error codes, I'll collect them into a HashSet. This automatically handles the deduplication for me.


use std::collections::HashSet;

let unique_codes: HashSet<&str> = logs.iter()
    .filter(|line| line.starts_with("ERROR"))
    .map(|line| line.split(':').nth(1).unwrap_or("Unknown").trim())
    .collect();

println!("Unique errors found: {:?}", unique_codes);
// Output: Unique errors found: {"404", "500", "403"}

If I just wanted to know how many errors there were without keeping the codes, I could have swapped .collect() for .count(). It's much more efficient because Rust doesn't have to allocate memory for a new collection; it just increments a counter as the items fly through the pipeline.




📋 Practical Task

Exercise: Discounted Luxury Item Total

You are building a checkout system for a high-end store. You have a list of product prices as a Vec<f64>. Your goal is to calculate the total cost of "luxury items" after a discount is applied.

Write a program that does the following using an iterator chain:

  • Starts with this dataset: let prices = vec![12.50, 150.00, 8.00, 200.00, 45.00, 500.00];
  • Filters out any item that costs less than $100.00.
  • Applies a 15% discount to the remaining luxury items (multiply by 0.85).
  • Sums the final total using the .sum() consuming adapter.

Print the final total to the console. Ensure you are using a chain of adapters rather than a for loop.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.