閉包

閉包(Closure)就像不需要名字的快速函式。有時它們被稱為 lambda。閉包很容易辨識,因為它們使用 || 而不是 ()。它們在 Rust 中非常常見,一旦你學會了使用它們,你就會愛不釋手。

你可以將閉包連結到變數上,而當你使用它時,它看起來就像一個函式一樣:

fn main() {
    let my_closure = || println!("This is a closure");
    my_closure();
}

所以這個閉包沒有接受東西:||,並印出訊息。This is a closure

|| 之間我們可以加上要輸入的變數和型別,就像在函式的 () 裡面一樣的用法:

fn main() {
    let my_closure = |x: i32| println!("{}", x);

    my_closure(5);
    my_closure(5+5);
}

印出:

5
10

當閉包變得更加復雜時,你可以加上程式碼區塊。那你就可以要寫多長就多長。

fn main() {
    let my_closure = || {
        let number = 7;
        let other_number = 10;
        println!("The two numbers are {} and {}.", number, other_number);
          // 這個閉包你想要寫多長就能有多長, 就像函式.
    };

    my_closure();
}

但是閉包的特殊在於它可以接受閉包之外的變數,即使你只有寫 ||。所以你可以這樣做:

fn main() {
    let number_one = 6;
    let number_two = 10;

    let my_closure = || println!("{}", number_one + number_two);
    my_closure();
}

就會印出 16。你不需要在 || 中放入任何東西,因為它可以直接拿到 number_onenumber_two 並把它們加起來。

順帶一提,這就是 閉包(closure) 這個名字的由來,因為它們會取得變數並將它們"封入(enclose)"在裡面。如果你想要很正確的說法:

  • || 如果不把變數從外面封進來就是"匿名函式(anonymous function)"。匿名的意思是"沒有名字"。它用起來更像個正規函式。
  • || 有從外部封入變數的才是"閉包"。它把它周圍的變數"封起來"使用。

但是人們經常會把所有的 || 函式都叫做閉包,所以你不用擔心名字的問題。我們只會對帶有 || 的任何東西叫"閉包",但請記住,它可能意味著一個"匿名函式"。

為什麼知道兩者的區別有益呢?因為匿名函式其實和具名函式產生一樣的機器碼(machine code)。它們給人的感覺是"高層抽象",所以有時候大家會覺得機器碼會很複雜。但是 Rust 用它產生的機器碼其實和正規函式一樣快。

所以讓我們再來看看更多一些閉包能做的事。你也可以這樣做:

fn main() {
    let number_one = 6;
    let number_two = 10;

    let my_closure = |x: i32| println!("{}", number_one + number_two + x);
    my_closure(5);
}

這個閉包取用 number_onenumber_two。我們還給了它新的變數 x,並且照範例來說 x 是 5。然後它把這三個都加在一起印出 21

通常在 Rust 中,你會在方法的引數裡面看到閉包,是因為用閉包作為引數是非常方便的事。我們在上個章節中有 .map().for_each() 的地方看到了閉包。在那個章節中,我們寫了 |x| 來代入疊代器的下一個元素,那就是一個閉包。

這裡是另一個範例:如果 unwrap 不起作用,可以用我們已知的 unwrap_or 方法給出一個值替代。之前我們寫的是:let fourth = my_vec.get(3).unwrap_or(&0);。但還有個引數是用閉包的 unwrap_or_else 方法。所以你可以這樣做:

fn main() {
    let my_vec = vec![8, 9, 10];

    let fourth = my_vec.get(3).unwrap_or_else(|| { // 試著 unwrap. 如果它不能用,
        if my_vec.get(0).is_some() {               // 就看 my_vec 是否有東西在索引 [0]
            &my_vec[0]                             // 如果有東西就回傳在索引 [0] 的數值
        } else {
            &0 // 不然就給 &0
        }
    });

    println!("{}", fourth);
}

當然,閉包也可以很簡單。例如你可以只寫 let fourth = my_vec.get(3).unwrap_or_else(|| &0);。你不必只因為有閉包,就總是需要用 {} 並寫出複雜的程式碼。只要你把 || 放進去,編譯器就知道你放了你需要的閉包。

最常用的閉包方法可能是 .map()。讓我們再來看看它。下面是一種使用方式:

fn main() {
    let num_vec = vec![2, 4, 6];

    let double_vec = num_vec        // 拿 num_vec
        .iter()                     // 疊代它
        .map(|number| number * 2)   // 對每個元素乘以二
        .collect::<Vec<i32>>();     // 然後從結果做新的 Vec
    println!("{:?}", double_vec);
}

另一個好例子是在 .enumerate() 之後使用 .for_each().enumerate() 方法給的是帶有索引號碼和元素的疊代器。例如:[10, 9, 8] 變成 (0, 10), (1, 9), (2, 8)。這裡每個元素的型別是 (usize, i32)。所以你可以這樣做:

fn main() {
    let num_vec = vec![10, 9, 8];

    num_vec
        .iter()      // 疊代 num_vec
        .enumerate() // 得到 (index, number)
        .for_each(|(index, number)| println!("Index number {} has number {}", index, number)); // 對每一個做些事
}

印出:

Index number 0 has number 10
Index number 1 has number 9
Index number 2 has number 8

在這種情況下,我們用 for_each 代替 mapmap 是用於對每個元素做一些事情,並將其傳遞出去,而 for_each當你看到每個元素時做一些事情。另外,map 不會做任何事情,除非你使用像 collect 這樣的方法。

其實,這就是疊代器有趣的地方。如果你試著用 map 之後卻沒用像 collect 這樣的方法,編譯器會告訴你它不會做任何事。它不會恐慌,但編譯器只會告訴你什麼事都沒做。

fn main() {
    let num_vec = vec![10, 9, 8];

    num_vec
        .iter()
        .enumerate()
        .map(|(index, number)| println!("Index number {} has number {}", index, number));

}

它說:

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

這是個警告,所以不是錯誤:程式有正常執行。但是為什麼 num_vec 沒做任何事呢?我們可以看看型別就知道了。

  • let num_vec = vec![10, 9, 8]; 現在是個 Vec<i32>
  • .iter() 現在是個 Iter<i32>。所以它是個元素為 i32 的疊代器。
  • .enumerate() 現在是個 Enumerate<Iter<i32>>。所以它是 i32Iter 型別的 Enumerate 型別。
  • .map() 現在是個 Map<Enumerate<Iter<i32>>> 的型別。所以它是 i32Iter 型別的 Enumerate 型別的 Map 型別。

我們所做的只是個越來越複雜的結構體。所以這個 Map<Enumerate<Iter<i32>>> 結構體只是準備好,但只有在我們告訴它要做什麼事時才會處理好能用。Rust 這樣做是因為它需要保證足夠快。它不想這樣做:

  • 迭代向量中所有的 i32
  • 然後列舉出疊代器中所有的 i32
  • 然後對映所有列舉出的 i32

Rust 只想做一次計算,所以它建立結構體並等待。之後如果我們講了 .collect::<Vec<i32>>(),它就會知道該怎麼做,並開始動作。這就是 iterators are lazy and do nothing unless consumed 的意思。疊代器在你"消耗(consume)"它們(用完它們)之前不會做任何事情。

你甚至可以用 .collect() 建立像 HashMap 這樣複雜的東西,所以它非常強大。這裡是如何將兩個向量放進 HashMap 的範例。首先我們做兩個向量出來,然後我們會對它們使用 .into_iter() 來得到值的疊代器。接著我們使用 .zip() 方法。這個方法將兩個疊代器就像拉鍊一樣伴隨(attach)在一起,。最後我們使用 .collect() 來做出 HashMap

這裡是程式碼:

use std::collections::HashMap;

fn main() {
    let some_numbers = vec![0, 1, 2, 3, 4, 5]; // 是 Vec<i32>
    let some_words = vec!["zero", "one", "two", "three", "four", "five"]; // 是 Vec<&str>

    let number_word_hashmap = some_numbers
        .into_iter()                 // 現在是疊代器
        .zip(some_words.into_iter()) // .zip() 裡面我們放入另一個疊代器. 現在它們在一起了.
        .collect::<HashMap<_, _>>();

    println!("For key {} we get {}.", 2, number_word_hashmap.get(&2).unwrap());
}

印出:

For key 2 we get two.

你可以看到我們寫得是 <HashMap<_, _>>,因為那有足夠資訊讓 Rust 判斷出型別是 HashMap<i32, &str>。如果你想要寫成 .collect::<HashMap<i32, &str>>(); 也行,或者你偏好像這樣寫也可以:

use std::collections::HashMap;

fn main() {
    let some_numbers = vec![0, 1, 2, 3, 4, 5]; // 是 Vec<i32>
    let some_words = vec!["zero", "one", "two", "three", "four", "five"]; // 是 Vec<&str>
    let number_word_hashmap: HashMap<_, _> = some_numbers  // 因為我們在這裡告訴它型別...
        .into_iter()
        .zip(some_words.into_iter())
        .collect(); // 我們就不用在這裡告訴它
}

還有一種方法,就像 char.enumerate()char_indices()(Indices的意思是"索引")。你用它的方式是一樣的。讓我們假裝有個由許多3位數的數字組成的大字串。

fn main() {
    let numbers_together = "140399923481800622623218009598281";

    for (index, number) in numbers_together.char_indices() {
        match (index % 3, number) {
            (0..=1, number) => print!("{}", number), // 在特定餘數時只印出數字
            _ => print!("{}\t", number), // 不然就印出帶有定位空白的數字
        }
    }
}

印出 140 399 923 481 800 622 623 218 009 598 281

閉包裡的 |_|

有時你會在閉包裡面看到 |_|。這意味著這個閉包需要一個引數(比如 x),但你不想使用它。所以 |_| 意味著 "好吧,這個閉包接受一個引數,但我不會給它名字是因為我不在乎它"。

這裡的範例是當你不這樣做時會有的錯誤:

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 講說

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

編譯器其實會給你一些幫助:

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")));

這是很好的建議。如果你把 || 改成 |_| 就可以運作了。

閉包和疊代器的有用方法

一旦閉包讓你感到自在時,Rust 就會成為一種非常有趣的語言。有了閉包,你可以將方法互相 連結 起來,用很少的程式碼做很多事情。下面是一些我們還沒有見過的閉包和使用閉包的方法。

.filter():讓你保留疊代器中你想保留的元素。讓我們過濾一年之中的月份。

fn main() {
    let months = vec!["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

    let filtered_months = months
        .into_iter()                         // 做出疊代器
        .filter(|month| month.len() < 5)     // 我們不想要月份名的長度超過 5 個位元組.
                                             // 我們知道每個字母是一個位元組, 所以用 .len() 沒問題
        .filter(|month| month.contains("u")) // 還有我們只喜歡字母有 u 的月份
        .collect::<Vec<&str>>();

    println!("{:?}", filtered_months);
}

印出 ["June", "July"]

.filter_map():這叫做 filter_map() 是因為它做了 .filter().map()。傳入的閉包必須回傳 Option<T>, 接著 filter_map() 將會從每一個 Option 取出是 Some 的值。所以比如說你套用 .filter_map()vec![Some(2), None, Some(3)],它就會回傳 [2, 3]

我們將寫一個用到 Company 結構體的範例。每個公司都有個 name,所以這個欄位是 String,但是 CEO 可能最近已經辭職了。所以 ceo 欄位是 Option<String>。我們會 .filter_map() 一些公司,只保留 CEO 的名字。

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 了, 那我們現在就回傳 Self
        Self {
            name: name.to_string(),
            ceo,
        }
    }

    fn get_ceo(&self) -> Option<String> {
        self.ceo.clone() // 只回傳 CEO 的克隆(結構體沒有 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 需要 Option<T>
        .collect::<Vec<String>>();

    println!("{:?}", all_the_ceos);
}

印出 ["Unknown", "Doug Suttles"]

既然 .filter_map() 需要 Option,那麼 Result 呢?沒問題:有一個叫做 .ok() 的方法,可以把 Result 變成 Option。之所以叫 .ok(),是因為它能傳送的只是 Ok 的結果(Err 的資訊沒有了)。你記得Option 完整型別是 Option<T>,而 ResultResult<T, E>,同時有 OkErr 的資訊。所以當你使用 .ok() 時,任何 Err 的資訊都會丟棄,變成 None

使用 .parse() 就是這種情況的簡單範例,我們嘗試解析一些使用者輸入。.parse() 在這裡接受 &str,並試著把它變成 f32。它回傳了 Result,但我們用的是 filter_map(),所以只要丟掉錯誤就可以。任何 Err 都會變成 None,並且被 .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);
}

印出 [8.9, 8.0, 7.6]

.ok() 相對的是 .ok_or()ok_or_else()。這樣就把 Option 變成了 Result。之所以叫 .ok_or(),是因為 Result 給你 Ok Err,所以你必須讓它知道 Err 的值是多少。這是因為 Option 中的 None 沒有任何資訊。另外,你現在可以看到,這些方法的名稱中帶有 else 的部分意味著它接受閉包。

我們可以把我們的 OptionCompany 結構體中取出來,然後用這個方式把它變成 Result。對於長期的錯誤處理方式,最好是建立自己的錯誤型別。但在現在我們只給了它錯誤訊息,所以它就變成了 Result<String, &str>

// 在 main() 之前的一切都完全一樣
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![]; // 假裝我們也需要收集錯誤的結果

    company_vec
        .iter()
        .for_each(|company| results_vec.push(company.get_ceo().ok_or("No CEO found")));

    for item in results_vec {
        println!("{:?}", item);
    }
}

最大的變化在這行:

#![allow(unused)]
fn main() {
// 🚧
.for_each(|company| results_vec.push(company.get_ceo().ok_or("No CEO found")));
}

它的意思是:"每家公司都用 get_ceo(). 如果你拿得到,那就把 Ok 裡面的數值傳給你。如果沒有,就在 Err 裡面傳遞"No CEO found"。然後把它放到 vec 裡。"

所以當我們印出 results_vec 時,會得到這樣的結果:

Ok("Unknown")
Ok("Doug Suttles")
Err("No CEO found")
Err("No CEO found")

所以現在我們有了所有四個元素。現在讓我們使用 .ok_or_else(),這樣我們就能使用閉包,並得到更好的錯誤訊息。現在我們有空間使用 format! 來建立 String,並將公司名稱放在其中。然後我們回傳這個 String

// 在 main() 之前的一切都完全一樣
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);
    }
}

這樣我們就有了:

Ok("Unknown")
Ok("Doug Suttles")
Err("No CEO found for The Red-Headed League")
Err("No CEO found for Stark Enterprises")

.and_then() 是個很有用的方法,它接受 Option,然後讓你對它的值做一些事情,並把它傳遞出去。所以它的輸入是個 Option,輸出也是個 Option。這有點像一個安全的"解包(unwrap),然後做一些事情,然後再包起來"。

一個簡單的例子是,我們使用 .get() 從向量中得到的數字,因為它回傳的是 Option。現在我們可以把它傳給 and_then(),如果它是 Some,我們還可以對它做一些數學運算。如果是 None,那麼 None 就會被傳遞過去。

fn main() {
    let new_vec = vec![8, 9, 0]; // 只是有數字的向量

    let number_to_add = 5;       // 後面用這個來運算
    let mut empty_vec = vec![];  // 結果放進這裡


    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);
}

印出了 [Some(14), Some(15), Some(6), None, None]。你可以看到 None 並沒有被過濾掉,只是傳遞過去了。

.and() 有點像是 boolOption。你可以匹配很多個 Option,如果它們都是 Some,那麼它會給出最後一個。而如果其中一個是 None,那麼就會給出 None

首先這裡有個 bool 的範例來幫助想像。你可以看到,如果你用的是 &&(和),哪怕是一個 false,也會讓一切 false

fn main() {
    let one = true;
    let two = false;
    let three = true;
    let four = true;

    println!("{}", one && three); // 印出 true
    println!("{}", one && two && three && four); // 印出 false
}

現在這裡的 .and() 也是同樣的東西。想像一下,我們做了五次操作,並把結果放在 Vec<Option<&str>> 中。如果我們得到一個值,我們就把 Some("success!") 推到向量中。然後我們再多做兩次這樣的操作。之後我們只用 .and() 顯示每次是得到 Some 時的索引。

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]));
    }
}

印出:

None
None
Some("success!")
Some("success!")
None

第一個(索引 0)None,是因為在 second_try 中索引 0 有 None。第二個 None,是因為在 first_try 中有 None。下一個是 Some("success!"),是因為 first_trysecond trythird_try 中都沒有 None

.any().all() 在疊代器中非常容易使用。它們根據你的輸入回傳 bool 值。在這個例子中,我們做了一個非常大的向量(大約 20000 個元素),包含了從 'a''働' 的所有字元。然後我們建立函式來檢查是否有某個字元在其中。

接下來我們做一個比較小的向量,問它是否全部都是字母(用 .is_alphabetic() 方法)。然後我們問它是否所有的字元都小於韓文字 '행'

還要注意的是你要傳一個參考進去,因為 .iter() 也會給出參考,你需要用傳進去的 & 和另一個 & 進行比較。

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 < '행'));
}

印出:

Is i inside? true
Is 뷁 inside? false
Is 鑿 inside? false
All alphabetic? false
All less than the character 행? true

順便說,.any() 只檢查到它第一個匹配的元素,然後就停止了。如果它已經找到了匹配結果,它就不會檢查所有的元素。如果你要在向量上使用 .any(),最好把可能會匹配的元素放前面。或者你可以在 .iter() 之後使用 .rev() 來反向疊代。這是這樣的向量:

fn main() {
    let mut big_vec = vec![6; 1000];
    big_vec.push(5);
}

所以這個 Vec 有 1000 個 6,後面還有一個 5。讓我們假裝來用 .any() 看看它是否包含 5。首先讓我們確定 .rev() 有效。記住,Iterator 總是有 .next(),能讓你檢查它每次做了什麼。

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());
}

印出:

Some(5)
Some(6)

我們是對的:有一個 Some(5),然後開始 1000 個 Some(6)。所以我們可以這樣寫:

fn main() {
    let mut big_vec = vec![6; 1000];
    big_vec.push(5);

    println!("{:?}", big_vec.iter().rev().any(|&number| number == 5));
}

而且因為是 .rev(),所以它只呼叫 .next() 一次就停止。如果我們不用 .rev(),那麼它將呼叫 .next() 1001次才停止。這段程式碼秀出這件事:

fn main() {
    let mut big_vec = vec![6; 1000];
    big_vec.push(5);

    let mut counter = 0; // 開始計數
    let mut big_iter = big_vec.into_iter(); // 做出 Iterator

    loop {
        counter +=1;
        if big_iter.next() == Some(5) { // 持續呼叫 .next() 直到我們得到 Some(5)
            break;
        }
    }
    println!("Final counter is: {}", counter);
}

這裡印出 Final counter is: 1001,所以我們知道它必須呼叫 .next() 1001 次才能找到 5。

.find() 告訴你疊代器裡是否有某個東西,而 .position() 則告訴你它在哪裡。.find().any() 不同是因為它回傳裡面有值的 Option(或 None)。與此同時,.position() 也是帶有位置號碼的 Option,或著 None。換句話說:

  • .find(): "我會試著找給你"
  • .position():"我會試著找看看在哪裡告訴你"

這是簡單的範例:

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 接受參考, 所以我們給它 &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));

}

印出:

Some(30) // This is the number itself
None // No number inside times 2 == 30
Some(2) // This is the position
None

有了 .cycle() 你可以建立無窮迴圈的疊代器。這種型別的疊代器能和 .zip() 很好地結合在一起用來建立新東西,就像建立 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);
}

所以,即使 .cycle() 可能永遠不會結束,但當把它們 zip 在一起時,另一個疊代器只運作了六次。也就是說,.cycle() 所產生的疊代器不會再被 .next() 呼叫,所以六次之後就完成了。輸出:

[(0, "even"), (1, "odd"), (2, "even"), (3, "odd"), (4, "even"), (5, "odd")]

類似的事情也可以用沒有結尾的範圍來做到。如果你寫 0..,那麼你就建立出永不停止的範圍。你可以很容易地使用這個方法:

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);
}

兩者都是印出十個字元,但第二個跳過 1300 位置,印出亞美尼亞語的十個字母。

['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
['յ', 'ն', 'շ', 'ո', 'չ', 'պ', 'ջ', 'ռ', 'ս', 'վ']

另一種流行的方法叫做 .fold()。這個方法經常用於將疊代器中的元素加在一起,但你也可以做更多的事情。它和 .for_each() 有些類似。在 .fold() 中,你首先新增起始值 (如果你要把元素加在一起,那就是 0),再逗號,然後是閉包。閉包給你兩個元素:到目前為止的總和和下一個元素。首先這個簡單的範例秀出 .fold() 怎麼將元素加在一起:

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)
    );
}

過程是:

  • 第 1 步是從 0 開始,並加上下個數字:9。
  • 然後把 9 再加上 6:15。
  • 然後把 15 再加上 9: 24。
  • 然後把 24,再加上 10:34。
  • 最後把 34,再加上 11:45。所以它印出了 45

但是你不是只能用它來加上東西。在這裡的範例我們把每一個字元上加一個 '-',來做出 String

fn main() {
    let a_string = "I don't have any dashes in me.";

    println!(
        "{}",
        a_string
            .chars() // 現在是個疊代器了
            .fold("-".to_string(), |mut string_so_far, next_char| { // 從字串 "-" 開始. 每次把它代入成為可變的字串並跟著下個字元
                string_so_far.push(next_char); // 把字完推進去, 再來是 '-'
                string_so_far.push('-');
                string_so_far} // 別忘記傳到下一個迴圈
            ));
}

印出:

-I- -d-o-n-'-t- -h-a-v-e- -a-n-y- -d-a-s-h-e-s- -i-n- -m-e-.-

還有許多其他方便的方法,比如:

  • .take_while() 只要一直從閉包得到 true,就會帶元素到新的疊代器 (例如 take while x > 5)
  • .cloned() 會對疊代器內的元素做克隆。這將會把參考傳換成值。
  • .by_ref() 會讓疊代器取得參考。這很好的保證你在使用 Vec 或類似的東西來做疊代器後還可以使用它。
  • 許多其他名稱中有 _while 的方法:.skip_while().map_while() 等等。
  • .sum():就是把所有的東西加在一起。

.chunks().windows() 是將向量切割成你想要的尺寸的兩種方式。你把想要的尺寸放在括號裡。比如說你有個 10 個元素的向量,你想要 3 個的尺寸,它的工作原理是這樣:

  • .chunks() 會給你 4 個切片(slice): [0, 1, 2], 然後是 [3, 4, 5], 再來是 [6, 7, 8], 最後是 [9]。所以它會嘗試用三個元素做一個切片,但如果它沒有三個元素,那麼它也不會恐慌。它只會給你剩下的東西。

  • .windows() 會先給你一個 [0, 1, 2] 的切片。然後它將會移過去下一個元素,給你 [1, 2, 3]。它會一直這樣做到終於到達最後三個元素的切片時才停止。

所以讓我們在簡單的數字向量上使用它們。看起來像這樣:

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);
    }
}

印出:

[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]

順便說一下,如果你什麼都不給它,.chunks() 會恐慌。你可以寫 .chunks(1000) 給只有一個元素的向量,但你不能寫 .chunks() 給任何長度為 0 的東西。 如果你點選了文件裡的 [src] 你可以看到它就在函式原始碼之中,因為它說 assert!(chunk_size != 0);

.match_indices() 讓你把 String&str 裡面所有符合你的輸入的東西都拿出來,並給你索引。它與 .enumerate() 類似,因為它回傳包含兩個元素的元組。

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<(_, _)>>(); // 這是 Vec<usize, &str> 但我們只告訴 Rust 去決定
    println!("{:?}", rule_locations);
}

This prints:

[(0, "Rule"), (28, "Rule"), (62, "Rule")]

.peekable() 讓你建立可以偷看到 (peek at) 下一個元素的疊代器。除了疊代器不會移動外,它就像呼叫 .next() (它給你 Option),所以你可以隨意使用它。實際上你可以把 peekable 想成是 "可停止"的,因為你可以想停多久就停多久。這裡的範例是我們對每個元素都使用 .peek() 三次。我們可以永遠使用 .peek(),直到我們使用 .next() 移動到下一個元素。

fn main() {
    let just_numbers = vec![1, 5, 100];
    let mut number_iter = just_numbers.iter().peekable(); // 這裡實際上建立了一種叫作 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();
    }
}

印出:

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

這是另一個範例,我們使用 .peek() 匹配一個元素。使用完後,我們呼叫 .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() 給我們的是參考所以需要 *
                println!("Found a hamlet: {} with {} people", name, number)
            }
            Some((name, number)) => println!("Found a town: {} with {} people", name, number),
            None => break,
        }
        location_iter.next();
    }
}

印出:

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

最後,這個範例我們也有用 .match_indices()。在這個例子中,我們根據 &str 中的空格數,將名字放入 struct 中。

#[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 { // 開始空的 Names 結構體
        one_word: vec![],
        two_words: vec![],
        three_words: vec![],
    };

    while iter_of_names.peek().is_some() {
        let next_item = iter_of_names.next().unwrap(); // 我們可以用 .unwrap() 因為我們知道寫它是 Some
        match next_item.match_indices(' ').collect::<Vec<_>>().len() { // 用 .match_indices 建立快速向量並檢查長度
            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);
}

會印出:

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"] }