迭代器
迭代器是一个构造,它可以给你集合中的元素,一次一个。实际上,我们已经使用了很多迭代器:for
循环给你一个迭代器。当你想在其他时候使用迭代器时,你必须选择什么样的迭代器:
.iter()
引用的迭代器.iter_mut()
可变引用的迭代器.into_iter()
值的迭代器(不是引用)
for
循环其实只是一个拥有值的迭代器。这就是为什么可以让它变得可变,然后你可以在使用的时候改变值。
我们可以这样使用迭代器。
fn main() { let vector1 = vec![1, 2, 3]; // we will use .iter() and .into_iter() on this one let vector1_a = vector1.iter().map(|x| x + 1).collect::<Vec<i32>>(); let vector1_b = vector1.into_iter().map(|x| x * 10).collect::<Vec<i32>>(); let mut vector2 = vec![10, 20, 30]; // we will use .iter_mut() on this one vector2.iter_mut().for_each(|x| *x +=100); println!("{:?}", vector1_a); println!("{:?}", vector2); println!("{:?}", vector1_b); }
这个将打印:
[2, 3, 4]
[110, 120, 130]
[10, 20, 30]
前两个我们用了一个叫.map()
的方法。这个方法可以让你对每一个元素做一些事情,然后把它传递下去。最后我们用的是一个叫.for_each()
的方法。这个方法只是让你对每一个元素做一些事情。.iter_mut()
加上for_each()
基本上就是一个for
的循环。在每一个方法里面,我们可以给每一个元素起一个名字(我们刚才叫它 x
),然后用它来改变它。这些被称为闭包,我们将在下一节学习它们。
让我们再来看看它们,一次一个。
首先我们用.iter()
对vector1
进行引用。我们给每个元素都加了1,并使其成为一个新的Vec。vector1
还活着,因为我们只用了引用:我们没有按值取。现在我们有 vector1
,还有一个新的 Vec 叫 vector1_a
。因为.map()
只是传递了它,所以我们需要使用.collect()
把它变成一个Vec
。
然后我们用into_iter
从vector1
中按值得到一个迭代器。这样就破坏了vector1
,因为这就是into_iter()
的作用。所以我们做了vector1_b
之后,就不能再使用vector1
了。
最后我们在vector2
上使用.iter_mut()
。它是可变的,所以我们不需要使用.collect()
来创建一个新的Vec。相反,我们用可变引用改变同一Vec中的值。所以vector2
仍然存在。因为我们不需要一个新的Vec,我们使用for_each
:它就像一个for
循环。
迭代器如何工作
迭代器的工作原理是使用一个叫做 .next()
的方法,它给出一个 Option
。当你使用迭代器时,Rust会一遍又一遍地调用next()
。如果得到 Some
,它就会继续前进。如果得到 None
,它就停止。
你还记得 assert_eq!
宏吗?在文档中,你经常看到它。这里它展示了迭代器的工作原理。
fn main() { let my_vec = vec!['a', 'b', '거', '柳']; // Just a regular Vec let mut my_vec_iter = my_vec.iter(); // This is an Iterator type now, but we haven't called it yet assert_eq!(my_vec_iter.next(), Some(&'a')); // Call the first item with .next() assert_eq!(my_vec_iter.next(), Some(&'b')); // Call the next assert_eq!(my_vec_iter.next(), Some(&'거')); // Again assert_eq!(my_vec_iter.next(), Some(&'柳')); // Again assert_eq!(my_vec_iter.next(), None); // Nothing is left: just None assert_eq!(my_vec_iter.next(), None); // You can keep calling .next() but it will always be None }
为自己的struct或enum实现Iterator
并不难。首先我们创建一个书库,想一想。
#[derive(Debug)] // we want to print it with {:?} struct Library { library_type: LibraryType, // this is our enum books: Vec<String>, // list of books } #[derive(Debug)] enum LibraryType { // libraries can be city libraries or country libraries City, Country, } impl Library { fn add_book(&mut self, book: &str) { // we use add_book to add new books self.books.push(book.to_string()); // we take a &str and turn it into a String, then add it to the Vec } fn new() -> Self { // this creates a new Library Self { library_type: LibraryType::City, // most are in the city so we'll choose City // most of the time books: Vec::new(), } } } fn main() { let mut my_library = Library::new(); // make a new library my_library.add_book("The Doom of the Darksword"); // add some books my_library.add_book("Demian - die Geschichte einer Jugend"); my_library.add_book("구운몽"); my_library.add_book("吾輩は猫である"); println!("{:?}", my_library.books); // we can print our list of books }
这很好用。现在我们想为库实现Iterator
,这样我们就可以在for
循环中使用它。现在如果我们尝试 for
循环,它就无法工作。
#![allow(unused)] fn main() { for item in my_library { println!("{}", item); // ⚠️ } }
它说:
error[E0277]: `Library` is not an iterator
--> src\main.rs:47:16
|
47 | for item in my_library {
| ^^^^^^^^^^ `Library` is not an iterator
|
= help: the trait `std::iter::Iterator` is not implemented for `Library`
= note: required by `std::iter::IntoIterator::into_iter`
但是我们可以用impl Iterator for Library
把库做成迭代器。Iterator
trait的信息在标准库中。https://doc.rust-lang.org/std/iter/trait.Iterator.html
在页面的左上方写着:Associated Types: Item
和Required Methods: next
。"关联类型"的意思是 "一起使用的类型"。我们的关联类型将是String
,因为我们希望迭代器给我们提供String。
在页面中,它有一个看起来像这样的例子。
// an iterator which alternates between Some and None struct Alternate { state: i32, } impl Iterator for Alternate { type Item = i32; fn next(&mut self) -> Option<i32> { let val = self.state; self.state = self.state + 1; // if it's even, Some(i32), else None if val % 2 == 0 { Some(val) } else { None } } } fn main() {}
你可以看到impl Iterator for Alternate
下面写着type Item = i32
。这就是关联类型。我们的迭代器将针对我们的书籍列表,这是一个Vec<String>
。当我们调用next的时候。
它将给我们一个String
。所以我们就写type Item = String;
。这就是关联项。
为了实现 Iterator
,你需要写 fn next()
函数。这是你决定迭代器应该做什么的地方。对于我们的 Library
,我们首先希望它给我们最后一本书。所以我们将match
与.pop()
一起,如果是Some
的话,就把最后一项去掉。我们还想为每个元素打印 "is found!"。现在它看起来像这样:
#[derive(Debug, Clone)] struct Library { library_type: LibraryType, books: Vec<String>, } #[derive(Debug, Clone)] enum LibraryType { City, Country, } impl Library { fn add_book(&mut self, book: &str) { self.books.push(book.to_string()); } fn new() -> Self { Self { library_type: LibraryType::City, // most of the time books: Vec::new(), } } } impl Iterator for Library { type Item = String; fn next(&mut self) -> Option<String> { match self.books.pop() { Some(book) => Some(book + " is found!"), // Rust allows String + &str None => None, } } } fn main() { let mut my_library = Library::new(); my_library.add_book("The Doom of the Darksword"); my_library.add_book("Demian - die Geschichte einer Jugend"); my_library.add_book("구운몽"); my_library.add_book("吾輩は猫である"); for item in my_library.clone() { // we can use a for loop now. Give it a clone so Library won't be destroyed println!("{}", item); } }
这个打印:
吾輩は猫である is found!
구운몽 is found!
Demian - die Geschichte einer Jugend is found!
The Doom of the Darksword is found!