Rust 學習筆記:迭代器
- Rust 學習筆記:迭代器
- Iterator trait 和 next 方法
- 使用迭代器的方法
- 生成其他迭代器的方法
- 使用閉包捕獲它們的環境
Rust 學習筆記:迭代器
在 Rust 中,迭代器負責遍歷每個項的邏輯。迭代器是懶惰的,這意味著它們在調用消耗迭代器的方法之前沒有作用。
示例:
let v1 = vec![1, 2, 3];let v1_iter = v1.iter();for val in v1_iter {println!("Got: {val}");}
在這個示例中,我們將迭代器的創建與在 for 循環中使用迭代器分開。當使用 v1_iter 中的迭代器調用 for 循環時,迭代器中的每個元素都會在循環的一次迭代中使用,并打印出每個值。
Iterator trait 和 next 方法
所有迭代器都實現一個名為 Iterator 的 trait,該 trait 在標準庫中定義。這個特性的定義是這樣的:
pub trait Iterator {type Item;fn next(&mut self) -> Option<Self::Item>;// methods with default implementations elided
}
注意,這個定義使用了一些新的語法:type Item 和 Self::Item,它們定義了與這個 trait 相關聯的類型。這段代碼表明,實現 Iterator trait 還需要定義一個 Item 類型,并且該 Item 類型用于下一個方法的返回類型。換句話說,Item 類型將是迭代器返回的類型。
Iterator trait 只需要實現者定義一個方法:next 方法,每次返回迭代器的一個項,包裝在 Some 中,迭代結束時返回 None。
我們可以直接在迭代器上調用 next 方法。下面的代碼演示了在 vector 創建的迭代器上重復調用 next 會返回哪些值。
#[test]fn iterator_demonstration() {let v1 = vec![1, 2, 3];let mut v1_iter = v1.iter();assert_eq!(v1_iter.next(), Some(&1));assert_eq!(v1_iter.next(), Some(&2));assert_eq!(v1_iter.next(), Some(&3));assert_eq!(v1_iter.next(), None);}
注意,我們需要將 v1_iter 設置為可變的:在迭代器上調用 next 方法會改變迭代器用于跟蹤其在序列中的位置的內部狀態,每次調用都會消耗迭代器中的一個項。
在使用 for 循環時,不需要將 v1_iter 設為可變的,因為循環在后臺獲取了 v1_iter 的所有權并使其可變。
還要注意,從調用 next 得到的值是對 vector 中值的不可變引用。iter 方法在不可變引用上生成一個迭代器。如果要創建一個接受 v1 的所有權并返回擁有的值的迭代器,可以調用 into_iter 方法。類似地,如果要在可變引用上迭代,可以調用 iter_mut 方法。
使用迭代器的方法
Iterator trait 有許多不同的方法,由標準庫提供默認實現。其中一些方法在其定義中調用 next 方法,這就是為什么在實現 Iterator trait 時需要實現 next 方法的原因。
調用 next 的方法稱為消費適配器,因為調用它們會耗盡迭代器。
一個例子是 sum 方法,它獲得迭代器的所有權,并通過重復調用 next 來遍歷項,從而消耗迭代器。當它遍歷時,它將每個項目添加到運行總數中,并在迭代完成時返回總數。
#[test]fn iterator_sum() {let v1 = vec![1, 2, 3];let v1_iter = v1.iter();let total: i32 = v1_iter.sum();assert_eq!(total, 6);}
注意,在調用 sum 之后不允許使用 v1_iter,因為 sum 將獲得調用它的迭代器的所有權。
生成其他迭代器的方法
迭代器適配器是在 Iterator trait 上定義的不消耗迭代器的方法。相反,它們通過改變原始迭代器的某些方面來生成不同的迭代器。
下面代碼是調用迭代器適配器方法 map 的示例,該方法接受一個閉包,在遍歷每個項目時調用該閉包。map 方法返回一個新的迭代器,用于生成修改后的項。
let v1: Vec<i32> = vec![1, 2, 3];v1.iter().map(|x| x + 1);
運行代碼會得到一個警告:我們指定的閉包永遠不會被調用。這個警告提醒了我們原因:迭代器適配器是惰性的,我們需要在這里消費迭代器。
為了修復這個警告并消費迭代器,我們將使用 collect 方法,此方法使用迭代器并將結果值收集到集合數據類型中。
let v1: Vec<i32> = vec![1, 2, 3];let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();assert_eq!(v2, vec![2, 3, 4]);
因為 map 接受閉包,所以可以指定想對每個項執行的任何操作。
還可以將多個調用鏈接到迭代器適配器,以可讀的方式執行復雜的操作。但是,由于所有迭代器都是惰性的,因此必須調用一個使用適配器的方法來從調用迭代器適配器中獲取結果。
使用閉包捕獲它們的環境
許多迭代器適配器將閉包作為參數,通常我們指定作為迭代器適配器參數的閉包將是捕獲其環境的閉包。
示例:
#[derive(PartialEq, Debug)]
struct Shoe {size: u32,style: String,
}fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}#[cfg(test)]
mod tests {use super::*;#[test]fn filters_by_size() {let shoes = vec![Shoe {size: 10,style: String::from("sneaker"),},Shoe {size: 13,style: String::from("sandal"),},Shoe {size: 10,style: String::from("boot"),},];let in_my_size = shoes_in_size(shoes, 10);assert_eq!(in_my_size,vec![Shoe {size: 10,style: String::from("sneaker")},Shoe {size: 10,style: String::from("boot")},]);}
}
對于本例,我們將使用接受閉包的 filter 方法。閉包從迭代器獲取一個元素并返回 bool 值。如果閉包返回 true,則該值將包含在 filter 生成的迭代器中。如果閉包返回 false,則不包含該值。
在 shoes_in_size 的函數體中,調用 into_iter 來創建一個接受 vector 對象所有權的迭代器。然后調用 filter 將該迭代器改編為一個新的迭代器。filter 方法的閉包從環境中捕獲 shoe_size 參數,并將其值與每只鞋的尺寸進行比較,只保留指定尺寸的鞋子。最后,調用 collect 將經過調整的迭代器返回的值收集到函數返回的向量中。