How it works...

This recipe is incredibly important. No matter what you do, or which library you use, it's going to use iterators somewhere. All of the operations presented can be used on any collection and all types that implement the iterator trait.

In the first section, we looked at different ways to create iterators. I mention that ranges are limited because, in order to be iterable, the range-type has to implement Step. char doesn't, so you wouldn't be able to use 'A'..'D' as an iterator. For this reason, in line [209], we iterate over the characters as bytes:

    let alphabet: Vec<_> = (b'A' .. b'z' + 1) // Start as u8
.map(|c| c as char) // Convert all to chars
.filter(|c| c.is_alphabetic()) // Filter only alphabetic chars
.collect(); // Collect as Vec<char>

We have to set the limit of the range to b'z' + 1, because ranges are non-inclusive. You might have noticed that this fact makes using ranges confusing sometimes. This is why, on the nightly compiler, you can use inclusive ranges (Chapter 10, Using Experimental Nightly Features, Iterating over an inclusive range).

Let's return to our recipe here, though. While iterating, you have the option to use enumerate in order to get an iteration counter [22]. This is the reason that Rust gets away with not supporting traditional, C-style for loop syntax. You most probably have seen some variation of the following C code in some language or another:

for (int i = 0; i < some_length; i++) { 
...
}

Rust disallows this, because a range-based for loop is almost always cleaner to use, which you are going to know if you come from a Python background, as it pioneered this restriction. In fact, most programming languages have shifted their paradigm to promote range-based loops. In the rare cases where you really actually would like to know your iteration count, you can use enumerate to emulate this behavior.

When accessing single items with nth[41], you have to keep two things in mind:

  • It accesses an item by going through all items until it reaches the one you want. In the worst case, this is an access. If you can, use your collection's native access method (most of the time, this would be .get()).
  • It consumes the iterator up to the specified index. This means that calling nth twice with the exact same parameters is going to result in two different returned values [54]. Don't let this catch you by surprise.

Another thing to take note of when using an iterator's various accessors is that they all return an Option that will be None if the iterator has no more items left.

When collecting an iterator into a collection, the following two forms of annotation are completely equivalent:

let nums: Vec<_> = (1..10).collect();
let nums = (1..10).collect::<Vec<_>>();

Just use the one that you like most. In this book, we stick to the first form because of personal preference. The second form, by the way, is called the turbofish. This is because ::<> kinda looks like a certain family of fish. Cute, isn't it? Both forms are also able to deduce their exact type automatically, so you don't need to write Vec<i32>. The inner type can be omitted with an underscore (_), as shown.

cycle [163] takes an iterator and repeats it endlessly. [1, 2, 3] would become [1, 2, 3, 1, 2, 3, 1, ...].

zip [175] takes two iterators and creates one out of them by putting items that are at the same index into a tuple, and then chaining them. If the iterators have different sizes, it just ignores the extra items of the longer one. For example, [1, 2, 3] zipped with ['a', 'b', 'c', 'd'] would become [(1, 'a'), (2, 'b'), (3, 'c')], because 'd' will be thrown away as it has no partner to zip with. If you zip two infinite ranges, you will have no problem, as zip is lazy, which means it will only actually start zipping your iterators when it's really necessary; for example, when using take to extract the first few tuples [81].

If you need to mutate all of your items, you can use map. It can also be used to change the underlying type of the iterator, as shown in line [182]. for_each is really similar, with one big difference: it doesn't return anything. It's basically the same as manually using a for loop on an iterator. The intended use case for it is situations in which you have a lot of chained method calls on an iterator, where it can be more elegant to chain for_each as well, as a kind of consumer.

Iterators are very often chained together to weave complex transformations together. If you find yourself calling many methods on a single iterator, don't worry, as this is exactly what you should be doing. On the other hand, when you see yourself doing a lot of complicated stuff in a for loop, you should probably rewrite that code with iterators.

When using filter_map[199], you can keep an item by returning it, wrapped in Some. If you want to filter it out, return None. Before that, you're allowed to change the item in whatever way you want, which is the map part of the deal.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.69.199