閉包
閉包(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_one
和 number_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_one
和 number_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
代替 map
。map
是用於對每個元素做一些事情,並將其傳遞出去,而 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>>
。所以它是i32
的Iter
型別的Enumerate
型別。.map()
現在是個Map<Enumerate<Iter<i32>>>
的型別。所以它是i32
的Iter
型別的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>
,而 Result
是 Result<T, E>
,同時有 Ok
和 Err
的資訊。所以當你使用 .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 的部分意味著它接受閉包。
我們可以把我們的 Option
從 Company
結構體中取出來,然後用這個方式把它變成 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()
有點像是 bool
的 Option
。你可以匹配很多個 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_try
、second try
、third_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"] }