疊代器

疊代器是種可以一次拿給你集合中一個元素的構造。其實我們已經使用過疊代器很多次: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_itervector1 中得到取值疊代器。這樣就會銷毀 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: ItemRequired 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!