疊代器
疊代器是種可以一次拿給你集合中一個元素的構造。其實我們已經使用過疊代器很多次:for
迴圈就是給你疊代器使用。在其他時候當你想使用疊代器時,你必須選擇用那一種:
.iter()
給出參考的疊代器.iter_mut()
給出可變參考的疊代器.into_iter()
給出取值的疊代器(不是參考)
for
迴圈其實只是一個擁有值的疊代器。這就是為什麼它可以是可變的,並在使用的時候改變值。
我們可以像這樣使用疊代器:
fn main() { let vector1 = vec![1, 2, 3]; // 我們會對這個向量使用 .iter() 和 .into_iter() 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]; // 我們會對這個向量使用 .iter_mut() 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
),並用它的名字來改變它。這些被稱為閉包(closure),我們將在下個章節學到。
讓我們再來一個個看過它們一遍。
首先我們用 .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', '거', '柳']; // 只是正規的 Vec let mut my_vec_iter = my_vec.iter(); // 現在這是疊代器型別, 但我們還沒呼叫它 assert_eq!(my_vec_iter.next(), Some(&'a')); // 用 .next() 呼叫第一個元素 assert_eq!(my_vec_iter.next(), Some(&'b')); // 呼叫下一個 assert_eq!(my_vec_iter.next(), Some(&'거')); // 再一次 assert_eq!(my_vec_iter.next(), Some(&'柳')); // 再一次 assert_eq!(my_vec_iter.next(), None); // 沒有東西留下: 只有 None assert_eq!(my_vec_iter.next(), None); // 你能持續呼叫 .next() 但它會永遠是 None }
為自己的結構體或列舉實作 Iterator
並不太難。首先讓我們建立書庫,思考看看。
#[derive(Debug)] // 我們想用 {:?} 印出它 struct Library { library_type: LibraryType, // 這是我們的列舉 books: Vec<String>, // 書本清單 } #[derive(Debug)] enum LibraryType { // 書庫可以是城市圖書館或國家圖書館 City, Country, } impl Library { fn add_book(&mut self, book: &str) { // 我們用 add_book 來加入新書 self.books.push(book.to_string()); // 我們接受 &str 並回傳為 String, 再加入 Vec 裡 } fn new() -> Self { // 這裡建立新的 Library Self { library_type: LibraryType::City, // 多數是在城市裡所以 // 很多時候我們會選 City books: Vec::new(), } } } 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("吾輩は猫である"); println!("{:?}", my_library.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
特徵的資訊能在標準函式庫中查看:https://doc.rust-lang.org/std/iter/trait.Iterator.html
在頁面的左上方寫著:Associated Types: Item
和 Required Methods: next
。"關聯型別"的意思是"一起使用的型別"。我們的關聯型別將會是 String
,因為我們希望疊代器回傳給我們 String。
在頁面中,它有個看起來像這樣的範例。
// 交錯回傳 Some 和 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; // 如果是偶數回傳 Some(i32), 不然就是 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, // 很多時候 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 允許 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() { // 我們現在能用for迴圈. 給它克隆這樣Library就不會被銷毀 println!("{}", item); } }
印出:
吾輩は猫である is found!
구운몽 is found!
Demian - die Geschichte einer Jugend is found!
The Doom of the Darksword is found!