9 Iterators and closures again!

This chapter covers

Iterators and closures in Rust have so many methods that we need another full chapter to go over them. There are a lot of these methods, but it’s worth the effort to learn them because they do a lot of work for you. You might not memorize them all during your first reading, but if you remember their names and what they do, you can look them up later when you need them.

9.1 Helpful methods for closures and iterators

Rust becomes an even more fun language once you become comfortable with closures. As we saw in the last chapter, with closures you can “chain” methods to each other and do a lot of things with very little code. And the more of them you know, the more you can chain together. This chapter is mostly going to show you how to use certain common iterator methods that work conveniently with closures.

9.1.1 Mapping and filtering

Besides mapping, another common use case for using an iterator is filtering. While mapping lets you do something to and pass on each item in an iterator, filtering lets you keep only items that match a certain condition. There is even a method that enables you to do both at the same time. Let’s look at the main methods to do these operations, starting with the .filter() method.

The .filter() method allows you to keep the items in an iterator that you want based on an expression that returns a bool. Let’s give this a try by filtering the months of the year:

fn main() {
    let months = vec!["January", "February", "March", "April", "May",
    "June", "July", "August", "September", "October", "November",
    "December"];
 
    let filtered_months = months
        .into_iter()
        .filter(|month| month.len() < 5)       
        .filter(|month| month.contains("u"))   
        .collect::<Vec<&str>>();
 
    println!("{:?}", filtered_months);
}

For some reason, we don’t want months more than 5 bytes in length. We know that each letter is 1 byte, so using .len() is fine.

Also, we only like months with the letter u. You can .filter() and .filter() again as many times as you like.

This prints ["June", "July"].

Of course, you could also type .filter(|month| month.len() < 5 && month .contains("u")) to filter over one line. But this example shows that you can filter and filter again as much as you want.

The next method with a closure in it that we’ll learn has a pretty similar name: filter_map(). You can probably guess that its name is .filter_map() because it does both .filter() and .map(). Instead of a bool, the closure must return an Option<T>, and then .filter_map() takes the value out of each Option if it is Some. For example, if you were to .filter_map() and then collect() on a Vec holding Some(2), None, Some(3)], it would return [2, 3]. So that is why it uses Option: it filters out everything that is None. But it also maps because it passes the value on.

We will write an example with a Company struct. Each company has a name of type String, but the CEO might have recently quit, leaving the company without a leader. To represent this we can make the ceo field an Option<String>. We will .filter_ map() over some companies to just keep the CEO names:

struct Company {
    name: String,
    ceo: Option<String>,
}
 
impl Company {
    fn new(name: &str, ceo: &str) -> Self {
        let ceo = match ceo {
            "" => None,
            ceo => Some(ceo.to_string()),
        };                                             
        Self {
            name: name.to_string(),
            ceo,
        }
    }
 
    fn get_ceo(&self) -> Option<String> {
        self.ceo.clone()                               
    }
}
 
fn main() {
    let company_vec = vec![
        Company::new("Umbrella Corporation", "Unknown"),
        Company::new("Ovintiv", "Brendan McCracken"),
        Company::new("The Red-Headed League", ""),
        Company::new("Stark Enterprises", ""),
    ];
 
    let all_the_ceos = company_vec
        .iter()
        .filter_map(|company| company.get_ceo())       
        .collect::<Vec<String>>();
 
    println!("{:?}", all_the_ceos);
}

ceo is decided, so now we return Self.

Just returns a clone of the CEO (struct is not Copy)

filter_map needs Option<T>.

This prints ["Unknown", "Brendan McCracken"].

Since the closure inside .filter_map() needs to return an Option, what if you have a function that returns a Result? No problem: there is a method called .ok() that turns Result into Option. This method is probably called .ok() because all that can be passed on from a Result to an Option is the information inside an Ok result, as None doesn’t hold any information (thus, any Err information is gone). We can see this in the documentation for the .ok() method:

Converts from Result<T, E> to Option<T>.

Since you start out with a Result<T, E>, .ok() drops the E to turn it into an Option<T>, and any Err information that E had is now gone. If you had an Ok(some_ variable) and called .ok(), it would turn into a Some(some_variable); if you had an Err(some_err_variable), it would turn into None.

Using .parse() is an easy example of this, where we try to parse some user input into a number. In the next example, .parse() takes a &str and tries to turn it into an f32. It returns a Result, but we would like to use .filter_map() to filter out any parsing that didn’t work. Anything that returns an Err becomes None after the .ok() method and then gets filtered out by .filter_map().

fn main() {
let user_input = vec![
        "8.9",
        "Nine point nine five",
        "8.0",
        "7.6",
        "eleventy-twelve",
    ];
 
    let successful_numbers = user_input
        .iter()
        .filter_map(|input| input.parse::<f32>().ok())
        .collect::<Vec<f32>>();
 
    println!("{:?}", successful_numbers);
}

This prints [8.9, 8.0, 7.6].

On the opposite side of .ok() is .ok_or() and .ok_or_else(). Both of these methods turn an Option into a Result. This method is called .ok_or() because a Result gives an Ok or an Err, so you have to let it know what the Err value will be if it doesn’t return an Ok. After all, None in an Option doesn’t have any information to pass on, so we have to provide it.

In the last chapter, we saw the methods .unwrap_or() and .unwrap_or_else(), in which the _or_else method took a closure. You can see the same thing here: .ok_or_else() also takes a closure. This is the way a lot of methods in the standard library are named.

We can take our Option from the Company struct and turn it into a Result this way. For long-term error handling, it is good to create your own type of error. But for now, we will type a quick error message, which means that the method will return a Result<String, &str>:

struct Company {       
    name: String,
    ceo: Option<String>,
}
 
impl Company {
    fn new(name: &str, ceo: &str) -> Self {
        let ceo = match ceo {
            "" => None,
            ceo => Some(ceo.to_string()),
        };
        Self {
            name: name.to_string(),
            ceo,
        }
    }
 
    fn get_ceo(&self) -> Option<String> {
        self.ceo.clone()
    }
}
 
fn main() {
    let company_vec = vec![
        Company::new("Umbrella Corporation", "Unknown"),
        Company::new("Ovintiv", "Brendan McCracken"),
        Company::new("The Red-Headed League", ""),
        Company::new("Stark Enterprises", ""),
    ];
 
    let results: Vec<Result<String, &str>> = company_vec
        .iter()
        .map(|company| company.get_ceo().ok_or("No CEO found"))
        .collect();
 
    for item in results {
        println!("{:?}", item);
    }
}

Everything before main() in this example is exactly the same as the last example.

The following line is the biggest change:

.map(|company| company.get_ceo().ok_or("No CEO found"))

The line means “for each company, use .get_ceo() and turn it into a Result. If .get_ceo() returns a Some, pass on the value inside Ok. If .get_ceo() returns a None, pass on "No CEO found" inside Err.”

When we print the Vec results, we get this:

Ok("Unknown")
Ok("Brendan McCracken")
Err("No CEO found")
Err("No CEO found")

Now we have all four entries. Let’s use .ok_or_else() so we can use a closure and get a better error message since having a closure gives us the space to do whatever we want. We can use format! to create a String and put the company name in that. Then we return the String. (We could do anything else, too, because we have a whole closure to work with.) This is starting to look a bit more like real production code:

struct Company {
    name: String,
    ceo: Option<String>,
}
 
fn get_current_datetime() -> String {                            
    "2024-01-27T23:11:23".to_string()
}
 
impl Company {
    fn new(name: &str, ceo: &str) -> Self {
        let ceo = match ceo {
            "" => None,
            name => Some(name.to_string()),
        };
        Self {
            name: name.to_string(),
            ceo,
        }
    }
 
    fn get_ceo(&self) -> Option<String> {
        self.ceo.clone()
    }
}
 
fn main() {
    let company_vec = vec![
        Company::new("Umbrella Corporation", "Unknown"),
        Company::new("Ovintiv", "Brendan McCracken"),
        Company::new("The Red-Headed League", ""),
        Company::new("Stark Enterprises", ""),
    ];
 
    let results: Vec<Result<String, String>> = company_vec
        .iter()
        .map(|company| {
            company.get_ceo().ok_or_else(|| {                    
                let err_message = format!("No CEO found for {}",
    company.name);                                               
                println!("{err_message} at {}",
    get_current_datetime());                                     
                err_message                                      
            })
        })
        .collect();
 
    results                                                      
        .iter()
        .filter(|res| res.is_ok())
        .for_each(|res| println!("{res:?}"));
}

We haven’t learned to work with dates yet, so we’ll use a dummy function that gives a single date.

This time we are using ok_or_else, which gives a lot more room.

First, we’ll construct an error message.

Then we’ll log the message as well as the date and time that the error happened . . .

. . . and pass on err_message in case of an Err.

We’ve already logged the errors, so let’s just print out the Ok results this time. A quick .filter() and .for_each() will do the trick.

This gives us the following input:

No CEO found for The Red-Headed League at 2024-01-27T23:11:23
No CEO found for Stark Enterprises at 2024-01-27T23:11:23
Ok("Unknown")
Ok("Brendan McCracken")

9.1.2 Some more iterator and related methods

There are some methods that are commonly used inside iterators that work on Option and Result. You’ll see these inside methods like .map() a lot.

One of them is called .and_then(). This method is a helpful one that takes an Option and lets you do something to the value inside in case it is a Some and pass it on. Meanwhile, a None holds no value, so it will just be passed on. This method’s input is an Option, and its output is also an Option. It is sort of like a safe “unwrap if Some, do something to the value, and wrap again.”

The following code shows an array that holds some &str values. We’ll check the first five indexes in the array by using .get() to see whether there is an item at that index. We will try to parse the &str into a u32 and then make the u32 into a char. Because .and_then() expects an Option and not a Result, we can use .ok() to turn each Result into an Option along the way:

fn main() {
    let num_array = ["8", "9", "Hi", "9898989898"];
    let mut char_vec = vec![];                                     
 
    for index in 0..5 {
        char_vec.push(
            num_array
                .get(index)                                        
                .and_then(|number| number.parse::<u32>().ok())     
                .and_then(|number| char::try_from(number).ok()),   
        );
    }
    println!("{:?}", char_vec);
}

Results go in here.

.get() returns an Option

Next, we try to parse the number into a u32 and then use .ok() to turn it into an Option.

We do the same here.

The previous code prints

[Some('\u{8}'), Some('\t'), None, None, None]

Notice that None isn’t filtered out; it’s just passed on. Also, all the Err information has been removed, so

Each of these parts failed for different reasons, but all we see at the end is None.

Another method is .and(), which is sort of like a bool for Option. You can match many Options to each other, and if they are all Some, it will give the last one. But if one of them is a None, it will give None.

Here is a bool example to help you imagine. You can see that if you are using && (ampersands), even one false makes everything false:

fn main() {
    let one = true;
    let two = false;
    let three = true;
    let four = true;
 
    println!("{}", one && three);                  
    println!("{}", one && two && three && four);   
}

true and true: prints true

true and false and true and true: prints false

Here is something similar using the .and() method. Imagine we did five operations and put the results in an array that holds Option<&str> values. If we get a value, we push Some("Okay!") into the array. We do this two more times. After that, we use .and() to show only the indexes that got Some every time:

fn main() {
    let try_1 = [Some("Okay!"), None, Some("Okay!"), Some("Okay!"), None];
    let try_2 = [None, Some("Okay!"), Some("Okay!"), Some("Okay!"),
    Some("Okay!")];
    let try_3 = [Some("Okay!"), Some("Okay!"), Some("Okay!"),
    Some("Okay!"), None];
 
    for i in 0..try_1.len() {
        println!("{:?}", try_1[i].and(try_2[i]).and(try_3[i]));
    }
}

This prints

None
None
Some("Okay!")
Some("Okay!")
None

The first attempt (index 0) is None because there is a None for index 0 in try_2. The second is None because there is a None in first_try. The next is Some("Okay!") because there is no None for try_1, try_2, or try_3.

The .flatten() method is a convenient way to ignore all None or Err values in an iterator and only return the successful values. Let’s try parsing some strings into numbers again:

fn main() {
    for num in ["9", "nine", "ninety-nine", "9.9"]
        .into_iter()
        .map(|num| num.parse::<f32>())
    {
        println!("{num:?}");
    }
}

The output shows both the Ok and Err values:

Ok(9.0)
Err(ParseFloatError { kind: Invalid })
Err(ParseFloatError { kind: Invalid })
Ok(9.9)

That works great! However, if we don’t care about the Err values, we can add .flatten() to directly access the values inside Ok and ignore the rest:

fn main() {
    for num in ["9", "nine", "ninety-nine", "9.9"]
        .into_iter()
        .map(|num| num.parse::<f32>())
        .flatten()
    {
        println!("{num}");
    }
}

Now the output is much simpler—just the two successful f32 values:

9
9.9

Some more common methods tell you whether an iterator contains a certain item or whether all of the items satisfy a condition. Or you might want to know where a certain item is so you can access it later. Let’s look at those methods now.

9.1.3 Checking and finding items inside iterators

The next two iterator methods to learn are .any() and .all(), which simply return a bool depending on whether a condition is true for any of the items or all of the items.

In the following example, we’ll make a large Vec (about 20,000 items) with all the chars from 'a' to ''. Next, we will make a smaller Vec and ask it whether it is all alphabetic (with the .is_alphabetic() method). Then we will ask it whether all the characters are less than the Korean character ''.

Also, note that you put a reference in because .iter() gives a reference, and you need an & to compare with another &:

fn in_char_vec(char_vec: &Vec<char>, check: char) {
    println!(
        "Is {check} inside? {}",
        char_vec.iter().any(|&char| char == check)
    );
}
 
fn main() {
    let char_vec = ('a'..'').collect::<Vec<char>>();
    in_char_vec(&char_vec, 'i');
    in_char_vec(&char_vec, '');
    in_char_vec(&char_vec, '');
 
    let smaller_vec = ('A'..'z').collect::<Vec<char>>();
    println!(
        "All alphabetic? {}",
        smaller_vec.iter().all(|&x| x.is_alphabetic())
    );
    println!(
        "All less than the character ? {}",
        smaller_vec.iter().all(|&x| x < '')
    );
}

This prints

Is i inside? true
Is  inside? false
Is  inside? false
All alphabetic? false
All less than the character ? true

NOTE All alphabetic? returns false because there are a few nonalphabetic characters in between the last capital letter and the first lowercase letter.

As you might have guessed, .any() only checks until it finds one matching item, and then it stops—there’s no need to check the rest of the items at this point. This early stop is sometimes called a short circuit. That means that if you are going to use .any() on a Vec, it might be a good idea to push the items that might return true near the front. Or you can use .rev() after .iter() to reverse the iterator if you think that items that could return true might be closer to the end. Here’s one such Vec:

fn main() {
    let mut big_vec = vec![6; 1000];
    big_vec.push(5);
}

This Vec has a thousand 6s followed by one 5. Let’s pretend we want to use .any() to see whether it contains 5. First, let’s make sure that .rev() is working. Remember, an Iterator always has .next() that lets you check what it returns each time:

fn main() {
    let mut big_vec = vec![6; 1000];
    big_vec.push(5);
 
    let mut iterator = big_vec.iter().rev();
    assert_eq!(iterator.next(), Some(&5));
    assert_eq!(iterator.next(), Some(&6));
}

The code doesn’t panic, so we were right that a 5 is returned first, followed by a 6. So, if we were to write this:

fn main() {
    let mut big_vec = vec![6; 1000];
    big_vec.push(5);
 
    println!("{:?}", big_vec.iter().rev().any(|&number| number == 5));
}

because we used .rev(), it only calls .next() one time and stops. If we don’t use .rev(), it will call .next() 1,001 times before it stops. This code shows it:

fn main() {
    let mut big_vec = vec![6; 1000];
    big_vec.push(5);
 
    let mut num_loops = 0;                    
    let mut big_iter = big_vec.into_iter();   
    loop {
        num_loops +=1;
        if big_iter.next() == Some(5) {       
            break;
        }
    }
    println!("Number of loops: {num_loops}");
}

Starts counting

Makes it an iterator

Keeps calling .next() until we get Some(5)

This prints Number of loops: 1001, so we know that it had to call .next() 1,001 times before it found 5.

The next two iterator methods we will look at are called .find() and .position(). The .find() method returns an item if it can, while .position() simply tells you where it is. .find() is different from .any() because it returns an Option with the value inside (or None). Meanwhile, .position() is also an Option with the position number or None:

Here is a simple example that tries to find numbers that can be divided by 3, followed by numbers divided by 11:

fn main() {
    let num_vec = vec![10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
 
    println!("{:?}", num_vec.iter().find(|number| *number % 3 == 0));
    println!("{:?}", num_vec.iter().position(|number| *number % 3 == 0));
    println!("{:?}", num_vec.iter().find(|number| *number % 11 == 0));
    println!("{:?}", num_vec.iter().position(|number| *number % 11 == 0));
}

This prints

Some(30)
Some(2)
None
None

The first Some(30) and Some(2) are saying the following:

Finally, we’ll take a look at a whole bunch of other iterator methods. You can make iterators that run forever, zip two iterators together, cut them into pieces, add the items together, and more.

9.1.4 Cycling, zipping, folding, and more

With the .cycle() method, you can create an iterator that loops forever. This type of iterator works well with .zip() to create something new, like this example, which creates a Vec<(i32, &str)>:

fn main() {
    let even_odd_iter = ["even", "odd"].into_iter().cycle();    
 
    let even_odd_vec: Vec<(i32, &str)> = (0..=5)
        .zip(even_odd_iter)
        .collect();
    println!("{:?}", even_odd_vec);
}

This iterator will first return Some("even") and Some("odd") forever. It will never return None.

Even though even_odd_iter will never end, the other iterator only runs six times and thus the final Vec also only has six items. The output is

[(0, "even"), (1, "odd"), (2, "even"), (3, "odd"), (4, "even"), (5, "odd")]

Something similar can be done with a range that doesn’t have an ending. If you write 0.., you create a range (which is also an iterator) that never stops. This is pretty easy to use:

fn main() {
    let ten_chars: Vec<char> = ('a'..).take(10).collect();
    let skip_then_ten_chars: Vec<char> = ('a'..).skip(1300).take(10).collect();
 
    println!("{ten_chars:?}");
    println!("{skip_then_ten_chars:?}");
}

Both print 10 characters, but the second one skips 1,300 places and prints 10 letters in Armenian:

['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
['յ', 'ն', 'շ', 'ո', 'չ', 'պ', 'ջ', 'ռ', 'ս', 'վ']

Another popular method is called .fold(). This method is often used to add together the items in an iterator, but you can also do a lot more. The .fold() method is somewhat similar to .for_each() except that it returns a final value at the end. When using .fold(), you first add a starting value, then a comma, and then the closure. The closure gives you two items: the total so far and the next item. Here is a simple example showing .fold() to add items together:

fn main() {
    let some_numbers = vec![9, 6, 9, 10, 11];
 
    println!("{}", some_numbers
        .iter()
        .fold(0, |total_so_far, next_number| total_so_far + next_number)
    );
}

These steps explain the logic:

  1. Starts with 0 and adds the next number: 9

  2. Takes that 9 and adds the 6: 15

  3. Takes that 15 and adds the 9: 24

  4. Takes that 24 and adds the 10: 34

  5. Takes that 34 and adds the 11: 45

  6. Prints 45

But .fold() isn’t just useful for adding numbers. Here is another example where we use .fold() to aggregate (combine) some events into a single struct:

#[derive(Debug)]
struct CombinedEvents {
    num_of_events: u32,
    data: Vec<String>,
}
 
fn main() {
    let events = [
        "Went to grocery store",
        "Came home",
        "Fed cat",
        "Fed cat again",
    ];
 
    let empty_events = CombinedEvents {                             
        num_of_events: 0,
        data: vec![]
    };
 
    let combined_events =
        events
            .iter()
            .fold(empty_events, |mut total_events, next_event| {    
                total_events.num_of_events += 1;                    
                total_events.data.push(next_event.to_string());
                total_events
            });
    println!("{combined_events:#?}");
}

We’ll start with an empty CombinedEvents struct. You could also use #[derive(Default)] on top and then write CombinedEvents::default() to do the same thing.

.fold() needs a default value, which is the empty struct. Then, for every item in our events array, we get access to the CombinedEvents struct and the next event (a &str).

We increase the number of events by 1 every time, push the next event to the data field and pass on the struct so it is available for the next iteration.

This prints

CombinedEvents {
    num_of_events: 4,
    data: [
        "Went to grocery store",
        "Came home",
        "Fed cat",
        "Fed cat again",
    ],
}

There really are a lot of convenient methods for iterators. Here is a quick introduction to a few more:

.by_ref() is good if you want to use part of an iterator for something but leave the rest of it alone. For example, the .take() method takes a self, so it takes the whole iterator if you use it. But if you only want to take two items and leave the iterator alone, you can use .into_iter().by_ref().take(2). Here’s a quick example that fails to compile:

fn main() {
    let mut number_iter = [7, 8, 9, 10].into_iter();    
    let first_two = number_iter.take(2).collect::<Vec<_>>();
    let second_two = number_iter.take(2).collect::<Vec<_>>();
}

Oops! .take() took ownership of the data:

error[E0382]: use of moved value: `number_iter`
    --> src\main.rs:4:22
     |
2    |     let mut number_iter = [7, 8, 9, 10].into_iter();
     |         --------------- move occurs because `number_iter` has type
     `std::array::IntoIter<i32, 4>`, which does not implement the `Copy`
     trait
3    |     let first_two = number_iter.take(2).collect::<Vec<_>>();
     |                                 ------- `number_iter` moved due to
     this method call
4    |     let second_two = number_iter.take(2).collect::<Vec<_>>();
     |                      ^^^^^^^^^^^ value used here after move
     |

So we’ll use .by_ref() to fix it. Now .take() won’t take ownership anymore:

fn main() {
    let mut number_iter = [7, 8, 9, 10].into_iter();
    
    let first_two = number_iter.by_ref().take(2).collect::<Vec<_>>();
    let second_two = number_iter.take(2).collect::<Vec<_>>();
}

You can also create iterators made out of cut-up pieces of a Vec or array. The .chunks() and .windows() methods will let you do that. To use them, write the number of items you want in each piece inside the parentheses. Let’s say you have a vector with 10 items, and you want each piece to have a size of 3. Here is the difference between the two methods:

So let’s use them on a simple vector of numbers. It looks like this:

fn main() {
    let num_vec = vec![1, 2, 3, 4, 5, 6, 7];
 
    for chunk in num_vec.chunks(3) {
        println!("{:?}", chunk);
    }
    println!();
    for window in num_vec.windows(3) {
        println!("{:?}", window);
    }
}

This prints

[1, 2, 3]
[4, 5, 6]
[7]
 
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]

By the way, .chunks() will panic if you give it a zero. You can write .chunks(1000) for a vector with one item, but you can’t make a .chunks(0) with a length of 0. You can see that right in the function if you look at its source code (clicking on [src] will let you see this):

    pub fn chunks(&self, chunk_size: usize) -> Chunks<'_, T> {
        assert!(chunk_size != 0, "chunk size must be non-zero");    
        Chunks::new(self, chunk_size)
    }

There are a few parts of this code that we don’t understand yet, but this line is pretty clear: it will panic if given a 0.

The .match_indices() method is sort of like a combination of .find() and .position(), except that it doesn’t involve returning an Option. Instead, it returns a tuple of the index and the item that matches.

.match_indices() lets you pull out everything inside a String or &str that matches your input and gives you the index, too. It is similar to .enumerate() because it returns a tuple with two items. This method is interesting because it allows you to insert anything that matches a trait called Pattern. We don’t need to think too much about this trait here—just remember that &str, char, and even closures can be passed into this method. Here is a quick example:

fn main() {
    let some_str = "Er ist noch nicht erklärt. Aber es gibt Krieg. Verlaß
    dich drauf.";
    for (index, item) in some_str.match_indices(|c| c > 'z') {
        println!("{item} at {index}");
    }
    for (index, item) in some_str.match_indices(". ") {
        println!("'{item}' at index {index}");
    }
}

This prints

ä at 22
ß at 53
'. ' at index 26
'. ' at index 46

The .peekable() method lets you make an iterator where you can see (peek at) the next item. It’s like calling .next() (it gives an Option) except that the iterator doesn’t move, so you can use it as many times as you want. You can think of peekable as “stoppable” because you can stop for as long as you want. The next example is a simple one that shows that we can use .peek() forever until it is time to call .next() to move on to the next item:

fn main() {
    let just_numbers = vec![1, 5, 100];
    let mut number_iter = just_numbers.iter().peekable();   
 
    for _ in 0..3 {
        println!("I love the number {}", number_iter.peek().unwrap());
        println!("I really love the number {}", number_iter.peek().unwrap());
        println!("{} is such a nice number", number_iter.peek().unwrap());
        number_iter.next();
    }
}

This creates a type of iterator called Peekable, which has the .peek() method. Regular iterators can’t use .peek().

This prints

I love the number 1
I really love the number 1
1 is such a nice number
I love the number 5
I really love the number 5
5 is such a nice number
I love the number 100
I really love the number 100
100 is such a nice number

That should be enough iterator methods for one chapter. This covers the majority of the ones you’ll use daily. But what if you wanted to see a method for an iterator that you didn’t see here? First, take a look in the standard library (https://doc.rust-lang.org/std/iter/trait.Iterator.html) to see whether a method there fits what you need. If that doesn’t have what you want, check out the itertools crate (https://docs.rs/iter tools/latest/itertools/), which has a ton of other methods that might fit your needs. (We will learn how to use external crates in chapter 16.)

With iterator methods covered, we’ll finish off the chapter with something easy: a macro and a method that will help you with quick and easy debugging of your code.

9.2 The dbg! macro and .inspect

The dbg! macro is a very useful one that prints quick information. It is a good alternative to println! because it is faster to type and gives more information:

fn main() {
    let my_number = 8;
    dbg!(my_number);
}

This prints [src\main.rs:4] my_number = 8.

You can put dbg! in many other places and even wrap code in it. Look at this code for example:

fn main() {
    let mut my_number = 9;
    my_number += 10;
    let new_vec = vec![8, 9, 10];
    let double_vec = new_vec.iter().map(|x| x * 2).collect::<Vec<i32>>();
}

This code creates a new mutable number and changes it. Then it creates a Vec and uses .iter(), .map(), and .collect() to create a new Vec. Interestingly, we can put dbg! almost everywhere in this code. dbg! essentially asks the compiler: “What are you doing at this moment, and what expression is being returned?” This next code sample is the same as the previous one except we have put dbg! everywhere:

fn main() {
    let mut my_number = dbg!(9);
    dbg!(my_number += 10);
    let new_vec = dbg!(vec![8, 9, 10]);
    let double_vec = dbg!(new_vec.iter().map(|x| x * 2).collect::<Vec<i32>>());
    dbg!(double_vec);
}

Each line of the code, followed by the output from the dbg! macro, is

let mut my_number = dbg!(9);
[src\main.rs:3] 9 = 9

and

dbg!(my_number += 10);
[src\main.rs:4] my_number += 10 = ()

and

let new_vec = dbg!(vec![8, 9, 10]);
[src\main.rs:6] vec![8, 9, 10] = [
    8,
    9,
    10,
]

and

let double_vec = dbg!(new_vec.iter().map(|x| x * 2).collect::<Vec<i32>>());
[src\main.rs:8] new_vec.iter().map(|x| x * 2).collect::<Vec<i32>>() = [
    16,
    18,
    20,
]

which shows you the value of the expression, and

dbg!(double_vec);
[src\main.rs:10] double_vec = [
    16,
    18,
    20,
]

Another method called .inspect() is similar to dbg!, but it is used in iterators in a similar fashion to .map(). This method simply gives you the item to look at, which lets you print it or do whatever you want. For example, let’s look at our double_vec again:

fn main() {
    let new_vec = vec![8, 9, 10];
 
    let double_vec = new_vec
        .iter()
        .map(|x| x * 2)
        .collect::<Vec<i32>>();
}

We want to know more information about what the code is doing, so we add .inspect() in two places:

fn main() {
    let new_vec = vec![8, 9, 10];
 
    let double_vec = new_vec
        .iter()
        .inspect(|first_item| println!("The item is: {first_item}"))
        .map(|x| x * 2)
        .inspect(|next_item| println!("Then it is: {next_item}"))
        .collect::<Vec<i32>>();
}

This prints

The item is: 8
Then it is: 16
The item is: 9
Then it is: 18
The item is: 10
Then it is: 20

Because .inspect() takes a closure, we have as much space as we like to work with the item:

fn main() {
    let new_vec = vec![8, 9, 10];
 
    let double_vec = new_vec
        .iter()
        .inspect(|first_item| {
            println!("The item is: {first_item}");
            match **first_item % 2 {                
                0 => println!("It is even."),
                _ => println!("It is odd."),
            }
            println!("In binary it is {:b}.", first_item);
        })
        .map(|x| x * 2)
        .collect::<Vec<i32>>();
}

The first item is a &&i32 so we use **.

This prints

The item is: 8
It is even.
In binary it is 1000.
The item is: 9
It is odd.
In binary it is 1001.
The item is: 10
It is even.
In binary it is 1010.

This chapter probably gave you a feel for why Rust code looks so functional sometimes. The reason is that the more you know about iterators and closures, the more you want to use them, so code written by experienced Rust users tends to have a lot of these. As you use Rust, you’ll begin to think of chains of one method after another, much in the same way you think in English or your native language. Take this human-readable example of an operation we might want to do: “Make an iterator, keep everything greater than 5, multiply each item by 2, reverse the iterator, pull off the first 10 items, and collect them into a Vec.” You can do that with one method for each. This is pretty close to the way we think as humans and probably another reason why iterator methods are used so much in Rust.

The next chapter has two important concepts. The first is lifetimes, which you use to tell Rust how long a reference will live. The next is interior mutability, which lets you (safely!) mutate variables without needing to use the mut keyword.

Summary