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 aVec<i32>
..iter()
Now it is anIter<i32>
. So it is an iterator with items ofi32
..enumerate()
Now it is anEnumerate<Iter<i32>>
. So it is a typeEnumerate
of typeIter
ofi32
s..map()
Now it is a typeMap<Enumerate<Iter<i32>>>
. So it is a typeMap
of typeEnumerate
of typeIter
ofi32
s.
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
i32
s in the Vec - then enumerate over all the
i32
s from the iterator - then map over all the enumerated
i32
s
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 char
s: 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, ceo => Some(ceo.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, 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", "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 method 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 Option
s 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 getstrue
(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 aVec
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]
[8, 9, 0]
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 .match_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"] }