Closures

Closures are like quick functions that don't need a name. Sometimes they are called lambdas. Closures are easy to find because they use || instead of (). They are very common in Rust, and once you learn to use them you will wonder how you lived without them.

You can bind a closure to a variable, and then it looks exactly like a function when you use it:

fn main() {
    let my_closure = || println!("This is a closure");
    my_closure();
}

So this closure takes nothing: || and prints a message: This is a closure.

In between the || we can add input variables and types, like inside () for a function:

fn main() {
    let my_closure = |x: i32| println!("{}", x);

    my_closure(5);
    my_closure(5+5);
}

This prints:

5
10

When the closure becomes more complicated, you can add a code block. Then it can be as long as you want.

fn main() {
    let my_closure = || {
        let number = 7;
        let other_number = 10;
        println!("The two numbers are {} and {}.", number, other_number);
          // This closure can be as long as we want, just like a function.
    };

    my_closure();
}

But closures are special because they can take variables that are outside the closure even if you only write ||. So you can do this:

fn main() {
    let number_one = 6;
    let number_two = 10;

    let my_closure = || println!("{}", number_one + number_two);
    my_closure();
}

So this prints 16. You didn't need to put anything in || because it can just take number_one and number_two and add them.

By the way, that is where the name closure comes from, because they take variables and "enclose" them inside. And if you want to be very correct:

  • a || that doesn't enclose a variable from outside is an "anonymous function". Anonymous means "doesn't have a name". It works more like a regular function.
  • a || that does enclose a variable from outside is a "closure". It "encloses" the variables around it to use them.

But people will often call all || functions closures, so you don't have to worry about the name. We will just say "closure" for anything with a ||, but remember that it can mean an "anonymous function".

Why is it good to know the difference? It's because an anonymous function actually makes the same machine code as a function with a name. They feel "high level", so sometimes people think that the machine code will be complicated. But the machine code that Rust makes from it is just as fast as a regular function.

So let's look at some more things that closures can do. You can also do this:

fn main() {
    let number_one = 6;
    let number_two = 10;

    let my_closure = |x: i32| println!("{}", number_one + number_two + x);
    my_closure(5);
}

This closure takes number_one and number_two. We also gave it a new variable x and said that x is 5. Then it adds all three together to print 21.

Usually you see closures in Rust inside of a method, because it is very convenient to have a closure inside. We saw closures in the last section with .map() and .for_each(). In that section we wrote |x| to bring in the next item in an iterator, and that was a closure.

Here is another example: the unwrap_or method that we know that you can use to give a value if unwrap doesn't work. Before, we wrote: let fourth = my_vec.get(3).unwrap_or(&0);. But there is also an unwrap_or_else method that has a closure inside. So you can do this:

fn main() {
    let my_vec = vec![8, 9, 10];

    let fourth = my_vec.get(3).unwrap_or_else(|| { // try to unwrap. If it doesn't work,
        if my_vec.get(0).is_some() {               // see if my_vec has something at index [0]
            &my_vec[0]                             // Give the number at index 0 if there is something
        } else {
            &0 // otherwise give a &0
        }
    });

    println!("{}", fourth);
}

Of course, a closure can be very simple. You can just write let fourth = my_vec.get(3).unwrap_or_else(|| &0); for example. You don't always need to use a {} and write complicated code just because there is a closure. As long as you put the || in, the compiler knows that you have put in the closure that you need.

The most frequent closure method is maybe .map(). Let's take a look at it again. Here is one way to use it:

fn main() {
    let num_vec = vec![2, 4, 6];

    let double_vec = num_vec        // take num_vec
        .iter()                     // iterate over it
        .map(|number| number * 2)   // for each item, multiply by two
        .collect::<Vec<i32>>();     // then make a new Vec from this
    println!("{:?}", double_vec);
}

Another good example is with .for_each() after .enumerate(). The .enumerate() method gives an iterator with the index number and the item. For example: [10, 9, 8] becomes (0, 10), (1, 9), (2, 8). The type for each item here is (usize, i32). So you can do this:

fn main() {
    let num_vec = vec![10, 9, 8];

    num_vec
        .iter()      // iterate over num_vec
        .enumerate() // get (index, number)
        .for_each(|(index, number)| println!("Index number {} has number {}", index, number)); // do something for each one
}

This prints:

Index number 0 has number 10
Index number 1 has number 9
Index number 2 has number 8

In this case we use for_each instead of map. map is for doing something to each item and passing it on, and for_each is doing something when you see each item. Also, map doesn't do anything unless you use a method like collect.

Actually, this is the interesting thing about iterators. If you try to map without a method like collect, the compiler will tell you that it doesn't do anything. It won't panic, but the compiler will tell you that you didn't do anything.

fn main() {
    let num_vec = vec![10, 9, 8];

    num_vec
        .iter()
        .enumerate()
        .map(|(index, number)| println!("Index number {} has number {}", index, number));

}

It says:

warning: unused `std::iter::Map` that must be used
 --> src\main.rs:4:5
  |
4 | /     num_vec
5 | |         .iter()
6 | |         .enumerate()
7 | |         .map(|(index, number)| println!("Index number {} has number {}", index, number));
  | |_________________________________________________________________________________________^
  |
  = note: `#[warn(unused_must_use)]` on by default
  = note: iterators are lazy and do nothing unless consumed

This is a warning, so it's not an error: the program runs fine. But why doesn't num_vec do anything? We can look at the types to see.

  • let num_vec = vec![10, 9, 8]; Right now it is a Vec<i32>.
  • .iter() Now it is an Iter<i32>. So it is an iterator with items of i32.
  • .enumerate() Now it is an Enumerate<Iter<i32>>. So it is a type Enumerate of type Iter of i32s.
  • .map() Now it is a type Map<Enumerate<Iter<i32>>>. So it is a type Map of type Enumerate of type Iter of i32s.

All we did was make a more and more complicated structure. So this Map<Enumerate<Iter<i32>>> is a structure that is ready to go, but only when we tell it what to do. Rust does this because it needs to be fast. It doesn't want to do this:

  • iterate over all the i32s in the Vec
  • then enumerate over all the i32s from the iterator
  • then map over all the enumerated i32s

Rust only wants to do one calculation, so it creates the structure and waits. Then if we say .collect::<Vec<i32>>() it knows what to do, and starts moving. This is what iterators are lazy and do nothing unless consumed means. The iterators don't do anything until you "consume" them (use them up).

You can even create complicated things like HashMap using .collect(), so it is very powerful. Here is an example of how to put two vecs into a HashMap. First we make the two vectors, and then we will use .into_iter() on them to get an iterator of values. Then we use the .zip() method. This method takes two iterators and attaches them together, like a zipper. Finally, we use .collect() to make the HashMap.

Here is the code:

use std::collections::HashMap;

fn main() {
    let some_numbers = vec![0, 1, 2, 3, 4, 5]; // a Vec<i32>
    let some_words = vec!["zero", "one", "two", "three", "four", "five"]; // a Vec<&str>

    let number_word_hashmap = some_numbers
        .into_iter()                 // now it is an iter
        .zip(some_words.into_iter()) // inside .zip() we put in the other iter. Now they are together.
        .collect::<HashMap<_, _>>();

    println!("For key {} we get {}.", 2, number_word_hashmap.get(&2).unwrap());
}

This prints:

For key 2 we get two.

You can see that we wrote <HashMap<_, _>> because that is enough information for Rust to decide on the type HashMap<i32, &str>. You can write .collect::<HashMap<i32, &str>>(); if you want, or you can write it like this if you prefer:

use std::collections::HashMap;

fn main() {
    let some_numbers = vec![0, 1, 2, 3, 4, 5]; // a Vec<i32>
    let some_words = vec!["zero", "one", "two", "three", "four", "five"]; // a Vec<&str>
    let number_word_hashmap: HashMap<_, _> = some_numbers  // Because we tell it the type here...
        .into_iter()
        .zip(some_words.into_iter())
        .collect(); // we don't have to tell it here
}

There is another method that is like .enumerate() for chars: char_indices(). (Indices means "indexes"). You use it in the same way. Let's pretend we have a big string that made of 3-digit numbers.

fn main() {
    let numbers_together = "140399923481800622623218009598281";

    for (index, number) in numbers_together.char_indices() {
        match (index % 3, number) {
            (0..=1, number) => print!("{}", number), // just print the number if there is a remainder
            _ => print!("{}\t", number), // otherwise print the number with a tab space
        }
    }
}

This prints 140 399 923 481 800 622 623 218 009 598 281.

|_| in a closure

Sometimes you see |_| in a closure. This means that the closure needs an argument (like x), but you don't want to use it. So |_| means "Okay, this closure takes an argument but I won't give it a name because I don't care about it".

Here is an example of an error when you don't do that:

fn main() {
    let my_vec = vec![8, 9, 10];

    println!("{:?}", my_vec.iter().for_each(|| println!("We didn't use the variables at all"))); // ⚠️
}

Rust says that

error[E0593]: closure is expected to take 1 argument, but it takes 0 arguments
  --> src\main.rs:28:36
   |
28 |     println!("{:?}", my_vec.iter().for_each(|| println!("We didn't use the variables at all")));
   |                                    ^^^^^^^^ -- takes 0 arguments
   |                                    |
   |                                    expected closure that takes 1 argument

The compiler actually gives you some help:

help: consider changing the closure to take and ignore the expected argument
   |
28 |     println!("{:?}", my_vec.iter().for_each(|_| println!("We didn't use the variables at all")));

This is good advice. If you change || to |_| then it will work.

Helpful methods for closures and iterators

Rust becomes a very fun to language once you become comfortable with closures. With closures you can chain methods to each other and do a lot of things with very little code. Here are some closures and methods used with closures that we didn't see yet.

.filter(): This lets you keep the items in an iterator that you want to keep. Let's filter 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()                         // make an iter
        .filter(|month| month.len() < 5)     // We don't want months more than 5 bytes in length.
                                             // We know that each letter is one byte so .len() is fine
        .filter(|month| month.contains("u")) // Also we only like months with the letter u
        .collect::<Vec<&str>>();

    println!("{:?}", filtered_months);
}

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

.filter_map(). This is called filter_map() because it does .filter() and .map(). The closure must return an Option<T>, and then filter_map() takes the value out of each Option if it is Some. So for example if you were to .filter_map() a vec![Some(2), None, Some(3)], it would return [2, 3].

We will write an example with a Company struct. Each company has a name so that field is String, but the CEO might have recently quit. So the ceo field is 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,
            name => Some(name.to_string()),
        }; // ceo is decided, so now we return Self
        Self {
            name: name.to_string(),
            ceo,
        }
    }

    fn get_ceo(&self) -> Option<String> {
        self.ceo.clone() // Just returns a clone of the CEO (struct is not Copy)
    }
}

fn main() {
    let company_vec = vec![
        Company::new("Umbrella Corporation", "Unknown"),
        Company::new("Ovintiv", "Doug Suttles"),
        Company::new("The Red-Headed League", ""),
        Company::new("Stark Enterprises", ""),
    ];

    let all_the_ceos = company_vec
        .into_iter()
        .filter_map(|company| company.get_ceo()) // filter_map needs Option<T>
        .collect::<Vec<String>>();

    println!("{:?}", all_the_ceos);
}

This prints ["Unknown", "Doug Suttles"].

Since .filter_map() needs an Option, what about Result? No problem: there is a method called .ok() that turns Result into Option. It is called .ok() because all it can send is the Ok result (the Err information is gone). You remember that Option is Option<T> while Result is Result<T, E> with information for both Ok and Err. So when you use .ok(), any Err information is lost and it becomes None.

Using .parse() is an easy example for this, where we try to parse some user input. .parse() here takes a &str and tries to turn it into an f32. It returns a Result, but we are using filter_map() so we just throw out the errors. Anything that is Err becomes None and is filtered out by .filter_map().

fn main() {
    let user_input = vec!["8.9", "Nine point nine five", "8.0", "7.6", "eleventy-twelve"];

    let actual_numbers = user_input
        .into_iter()
        .filter_map(|input| input.parse::<f32>().ok())
        .collect::<Vec<f32>>();

    println!("{:?}", actual_numbers);
}

This prints [8.9, 8.0, 7.6].

On the opposite side of .ok() is .ok_or() and ok_or_else(). This turns an Option into a Result. It 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. That is because None in an Option doesn't have any information. Also, you can see now that the else part in the names of these methods means that it has a closure.

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 just give it an error message, so it becomes a Result<String, &str>.

// Everything before main() is exactly the same
struct Company {
    name: String,
    ceo: Option<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", "Doug Suttles"),
        Company::new("The Red-Headed League", ""),
        Company::new("Stark Enterprises", ""),
    ];

    let mut results_vec = vec![]; // Pretend we need to gather error results too

    company_vec
        .iter()
        .for_each(|company| results_vec.push(company.get_ceo().ok_or("No CEO found")));

    for item in results_vec {
        println!("{:?}", item);
    }
}

This line is the biggest change:


#![allow(unused)]
fn main() {
// 🚧
.for_each(|company| results_vec.push(company.get_ceo().ok_or("No CEO found")));
}

It means: "For each company, use get_ceo(). If you get it, then pass on the value inside Ok. And if you don't, pass on "No CEO found" inside Err. Then push this into the vec."

So when we print results_vec we get this:

Ok("Unknown")
Ok("Doug Suttles")
Err("No CEO found")
Err("No CEO found")

So now we have all four entries. Now let's use .ok_or_else() so we can use a closure and get a better error message. Now we have space to use format! to create a String, and put the company name in that. Then we return the String.

// Everything before main() is exactly the same
struct Company {
    name: String,
    ceo: Option<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", "Doug Suttles"),
        Company::new("The Red-Headed League", ""),
        Company::new("Stark Enterprises", ""),
    ];

    let mut results_vec = vec![];

    company_vec.iter().for_each(|company| {
        results_vec.push(company.get_ceo().ok_or_else(|| {
            let err_message = format!("No CEO found for {}", company.name);
            err_message
        }))
    });

    for item in results_vec {
        println!("{:?}", item);
    }
}

This gives us:

Ok("Unknown")
Ok("Doug Suttles")
Err("No CEO found for The Red-Headed League")
Err("No CEO found for Stark Enterprises")

.and_then() is a helpful message that takes an Option, then lets you do something to its value and pass it on. So its input is an Option, and its output is also an Option. It is sort of like a safe "unwrap, then do something, then wrap again".

An easy example is a number that we get from a vec using .get(), because that returns an Option. Now we can pass it to and_then(), and do some math on it if it is Some. If it is None, then the None just gets passed through.

fn main() {
    let new_vec = vec![8, 9, 0]; // just a vec with numbers

    let number_to_add = 5;       // use this in the math later
    let mut empty_vec = vec![];  // results go in here


    for index in 0..5 {
        empty_vec.push(
            new_vec
               .get(index)
                .and_then(|number| Some(number + 1))
                .and_then(|number| Some(number + number_to_add))
        );
    }
    println!("{:?}", empty_vec);
}

This prints [Some(14), Some(15), Some(6), None, None]. You can see that None isn't filtered out, just passed on.

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

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

fn main() {
    let one = true;
    let two = false;
    let three = true;
    let four = true;

    println!("{}", one && three); // prints true
    println!("{}", one && two && three && four); // prints false
}

Now here is the same thing with .and(). Imagine we did five operations and put the results in a Vec<Option<&str>>. If we get a value, we push Some("success!") to the vec. Then we do this two more times. After that we use .and() to only show the indexes that got Some every time.

fn main() {
    let first_try = vec![Some("success!"), None, Some("success!"), Some("success!"), None];
    let second_try = vec![None, Some("success!"), Some("success!"), Some("success!"), Some("success!")];
    let third_try = vec![Some("success!"), Some("success!"), Some("success!"), Some("success!"), None];

    for i in 0..first_try.len() {
        println!("{:?}", first_try[i].and(second_try[i]).and(third_try[i]));
    }
}

This prints:

None
None
Some("success!")
Some("success!")
None

The first one (index 0) is None because there is a None for index 0 in second_try. The second is None because there is a None in first_try. The next is Some("success!") because there is no None for first_try, second try, or third_try.

.any() and .all() are very easy to use in iterators. They return a bool depending on your input. In this example we make a very large vec (about 20,000 items) with all the characters from 'a' to '働'. Then we make a function to check if a character is inside it.

Next we make a smaller vec and ask it whether it is all alphabetic (with the .is_alphabetic() method). Then we ask it if 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 a & to compare with another &.

fn in_char_vec(char_vec: &Vec<char>, check: char) {
    println!("Is {} inside? {}", check, 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

By the way, .any() only checks until it finds one matching item, and then it stops. It won't check them all if it has already found a match. If you are going to use .any() on a Vec, it might be a good idea to push the items that might match near the front. Or you can use .rev() after .iter() to reverse the iterator. Here's one vec like that:

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

So this Vec has 1000 6 followed by one 5. Let's pretend that we want to use .any() to see if it contains 5. First let's make sure that .rev() is working. Remember, an Iterator always has .next() that lets you check what it does every time.

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

    let mut iterator = big_vec.iter().rev();
    println!("{:?}", iterator.next());
    println!("{:?}", iterator.next());
}

It prints:

Some(5)
Some(6)

We were right: there is one Some(5) and then the 1000 Some(6) start. So we can write this:

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

    println!("{:?}", big_vec.iter().rev().any(|&number| number == 5));
}

And because it's .rev(), it only calls .next() one time and stops. If we don't use .rev() then it will call .next() 1001 times before it stops. This code shows it:

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

    let mut counter = 0; // Start counting
    let mut big_iter = big_vec.into_iter(); // Make it an Iterator

    loop {
        counter +=1;
        if big_iter.next() == Some(5) { // Keep calling .next() until we get Some(5)
            break;
        }
    }
    println!("Final counter is: {}", counter);
}

This prints Final counter is: 1001 so we know that it had to call .next() 1001 times before it found 5.

.find() tells you if an iterator has something, and .position() 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. In other words:

  • .find(): "I'll try to get it for you"
  • .position(): "I'll try to find where it is for you"

Here is a simple example:

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)); // find takes a reference, so we give it &number
    println!("{:?}", num_vec.iter().find(|&number| number * 2 == 30));

    println!("{:?}", num_vec.iter().position(|&number| number % 3 == 0));
    println!("{:?}", num_vec.iter().position(|&number| number * 2 == 30));

}

This prints:

Some(30) // This is the number itself
None // No number inside times 2 == 30
Some(2) // This is the position
None

With .cycle() 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 = vec!["even", "odd"];

    let even_odd_vec = (0..6)
        .zip(even_odd.into_iter().cycle())
        .collect::<Vec<(i32, &str)>>();
    println!("{:?}", even_odd_vec);
}

So even though .cycle() might never end, the other iterator only runs six times when zipping them together. That means that the iterator made by .cycle() doesn't get a .next() call again so it is done after six times. 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.. then you create a range that never stops. You can use this very easily:

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

    println!("{:?}", ten_chars);
    println!("{:?}", skip_then_ten_chars);
}

Both print ten characters, but the second one skipped 1300 places and prints ten letters in Armenian.

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

Another popular method is called .fold(). This method is used a lot to add together the items in an iterator, but you can also do a lot more. It is somewhat similar to .for_each(). In .fold(), you first add a starting value (if you are adding items together, then 0), then a comma, then the closure. The closure gives you two items: the total so far, and the next item. First 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)
    );
}

So:

  • on step 1 it starts with 0 and adds the next number: 9.
  • Then it takes that 9 and adds the 6: 15.
  • Then it takes that 15, and adds the 9: 24.
  • Then it takes that 24, and adds the 10: 34.
  • Finally it takes that 34, and adds the 11: 45. So it prints 45.

But you don't just need to add things with it. Here is an example where we add a '-' to every character to make a String.

fn main() {
    let a_string = "I don't have any dashes in me.";

    println!(
        "{}",
        a_string
            .chars() // Now it's an iterator
            .fold("-".to_string(), |mut string_so_far, next_char| { // Start with a String "-". Bring it in as mutable each time along with the next char
                string_so_far.push(next_char); // Push the char on, then '-'
                string_so_far.push('-');
                string_so_far} // Don't forget to pass it on to the next loop
            ));
}

This prints:

-I- -d-o-n-'-t- -h-a-v-e- -a-n-y- -d-a-s-h-e-s- -i-n- -m-e-.-

There are many other convenient methods like:

  • .take_while() which takes into an iterator as long as it gets true (take while x > 5 for example)
  • .cloned() which makes a clone inside the iterator. This turns a reference into a value.
  • .by_ref() which makes an iterator take a reference. This is good to make sure that you can use a Vec or something similar after you use it to make an iterator.
  • Many other _while methods: .skip_while(), .map_while(), and so on
  • .sum(): just adds everything together.

.chunks() and .windows() are two ways of cutting up a vector into a size you want. You put the size you want into the brackets. Let's say you have a vector with 10 items, and you want a size of 3. It will work like this:

  • .chunks() will give you four slices: [0, 1, 2], then [3, 4, 5], then [6, 7, 8], and finally [9]. So it will try to make a slice of three items, but if it doesn't have three then it won't panic. It will just give you what is left.

  • .windows() will first give you a slice of [0, 1, 2]. Then it will move over one and give you [1, 2, 3]. It will do that until it finally reaches the last slice of three and stop.

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, 8, 9, 0];

    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, 8, 9]
[0]

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

By the way, .chunks() will panic if you give it nothing. You can write .chunks(1000) for a vector with one item, but you can't write .chunks() with anything with a length of 0. You can see that right in the function if you click on [src] because it says assert!(chunk_size != 0);.

.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.

fn main() {
    let rules = "Rule number 1: No fighting. Rule number 2: Go to bed at 8 pm. Rule number 3: Wake up at 6 am.";
    let rule_locations = rules.match_indices("Rule").collect::<Vec<(_, _)>>(); // This is Vec<usize, &str> but we just tell Rust to do it
    println!("{:?}", rule_locations);
}

This prints:

[(0, "Rule"), (28, "Rule"), (62, "Rule")]

.peekable() 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 actually think of peekable as "stoppable", because you can stop for as long as you want. Here is an example of us using .peek() three times for every item. We can use .peek() forever until we use .next() to move to the next item.

fn main() {
    let just_numbers = vec![1, 5, 100];
    let mut number_iter = just_numbers.iter().peekable(); // This actually creates a type of iterator called 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 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

Here is another example where we use .peek() to match on an item. After we are done using it, we call .next().

fn main() {
    let locations = vec![
        ("Nevis", 25),
        ("Taber", 8428),
        ("Markerville", 45),
        ("Cardston", 3585),
    ];
    let mut location_iter = locations.iter().peekable();
    while location_iter.peek().is_some() {
        match location_iter.peek() {
            Some((name, number)) if *number < 100 => { // .peek() gives us a reference so we need *
                println!("Found a hamlet: {} with {} people", name, number)
            }
            Some((name, number)) => println!("Found a town: {} with {} people", name, number),
            None => break,
        }
        location_iter.next();
    }
}

This prints:

Found a hamlet: Nevis with 25 people
Found a town: Taber with 8428 people
Found a hamlet: Markerville with 45 people
Found a town: Cardston with 3585 people

Finally, here is an example where we also use .matches_indices(). In this example we put names into a struct depending on the number of spaces in the &str.

#[derive(Debug)]
struct Names {
    one_word: Vec<String>,
    two_words: Vec<String>,
    three_words: Vec<String>,
}

fn main() {
    let vec_of_names = vec![
        "Caesar",
        "Frodo Baggins",
        "Bilbo Baggins",
        "Jean-Luc Picard",
        "Data",
        "Rand Al'Thor",
        "Paul Atreides",
        "Barack Hussein Obama",
        "Bill Jefferson Clinton",
    ];

    let mut iter_of_names = vec_of_names.iter().peekable();

    let mut all_names = Names { // start an empty Names struct
        one_word: vec![],
        two_words: vec![],
        three_words: vec![],
    };

    while iter_of_names.peek().is_some() {
        let next_item = iter_of_names.next().unwrap(); // We can use .unwrap() because we know it is Some
        match next_item.match_indices(' ').collect::<Vec<_>>().len() { // Create a quick vec using .match_indices and check the length
            0 => all_names.one_word.push(next_item.to_string()),
            1 => all_names.two_words.push(next_item.to_string()),
            _ => all_names.three_words.push(next_item.to_string()),
        }
    }

    println!("{:?}", all_names);
}

This will print:

Names { one_word: ["Caesar", "Data"], two_words: ["Frodo Baggins", "Bilbo Baggins", "Jean-Luc Picard", "Rand Al\'Thor", "Paul Atreides"], three_words:
["Barack Hussein Obama", "Bill Jefferson Clinton"] }