Rust 集合型別:集合的迭代與遍歷
簡介
在日常開發中,**集合(Collection)**是最常使用的資料結構之一。無論是儲存使用者輸入、處理檔案內容,或是實作演算法,都離不開 Vec<T>、HashMap<K, V>、HashSet<T> 等容器。Rust 為了在保證安全性的同時提供高效能,對集合的 迭代(iteration) 與 遍歷(traversal) 加入了許多語言層面的支援與最佳化。
本單元將帶你了解:
- 為什麼迭代是 Rust 中最常見且最安全的資料存取方式。
- 如何使用不同的迭代器(
iter、iter_mut、into_iter)以及常見的 適配器(adapter)(如map、filter、enumerate)。 - 在實務開發中避免常見的所有權與借用陷阱,寫出既簡潔又高效的遍歷程式碼。
核心概念
1. 迭代器的三種取得方式
| 取得方式 | 取得的迭代器型別 | 典型用途 |
|---|---|---|
iter() |
std::slice::Iter<'a, T>(不可變) |
只讀集合內容 |
iter_mut() |
std::slice::IterMut<'a, T>(可變) |
需要修改元素 |
into_iter() |
std::vec::IntoIter<T>(消費) |
取得所有權、搬移元素 |
重點:
into_iter會把集合「消費」掉,之後集合就無法再使用;而iter/iter_mut只借用集合,原集合仍然可用。
範例 1:iter 與 for 迴圈
let numbers = vec![1, 2, 3, 4, 5];
// 使用 iter() 取得不可變迭代器
for n in numbers.iter() {
println!("目前的數字是 {}", n);
}
// numbers 仍然可以使用
println!("集合長度 = {}", numbers.len());
範例 2:iter_mut 修改元素
let mut scores = vec![10, 20, 30];
// 取得可變迭代器,直接改變每個元素
for s in scores.iter_mut() {
*s += 5; // 必須解引用才能寫入
}
assert_eq!(scores, vec![15, 25, 35]);
範例 3:into_iter 消費集合
let names = vec!["Alice", "Bob", "Carol"];
// 直接搬移字串所有權到迭代器中
let uppercased: Vec<String> = names
.into_iter()
.map(|s| s.to_uppercase())
.collect();
// 此時 names 已無法再使用
println!("{:?}", uppercased); // ["ALICE", "BOB", "CAROL"]
2. 迭代器適配器(Adapter)
迭代器本身只提供 next() 方法,但 Rust 標準庫提供了大量 適配器,讓我們可以在遍歷過程中完成過濾、映射、累加等操作,而不必寫出繁瑣的 for 迴圈。
2.1 map – 轉換每個元素
let nums = vec![1, 2, 3];
let squares: Vec<i32> = nums.iter().map(|x| x * x).collect();
assert_eq!(squares, vec![1, 4, 9]);
2.2 filter – 只保留符合條件的元素
let words = vec!["rust", "go", "python", "js"];
let long_words: Vec<&&str> = words.iter()
.filter(|w| w.len() > 3)
.collect();
assert_eq!(long_words, vec![&&"rust", &&"python"]);
2.3 enumerate – 同時取得索引
let chars = vec!['a', 'b', 'c'];
for (idx, ch) in chars.iter().enumerate() {
println!("第 {} 個字元是 {}", idx, ch);
}
2.4 fold – 累積計算
let values = vec![2, 4, 6];
let product = values.iter().fold(1, |acc, &x| acc * x);
assert_eq!(product, 48);
3. for 迴圈的語法糖
在 Rust 中,for 迴圈其實是 IntoIterator trait 的語法糖。只要類型實作了 IntoIterator,就可以直接在 for 中使用。
// 自訂結構實作 IntoIterator
struct Counter {
current: usize,
max: usize,
}
impl Counter {
fn new(max: usize) -> Self {
Counter { current: 0, max }
}
}
impl Iterator for Counter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.max {
self.current += 1;
Some(self.current)
} else {
None
}
}
}
// 直接在 for 迴圈中使用
for i in Counter::new(3) {
println!("計數: {}", i);
}
4. 同時遍歷多個集合
zip 可以把兩個迭代器「配對」起來,常用於同時處理兩個等長集合。
let xs = vec![1, 2, 3];
let ys = vec![4, 5, 6];
let summed: Vec<i32> = xs.iter()
.zip(ys.iter())
.map(|(a, b)| a + b)
.collect();
assert_eq!(summed, vec![5, 7, 9]);
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 迭代器消費後集合無法使用 | 使用 into_iter 之後,原集合已被搬走。 |
若仍需集合,改用 iter 或先 clone()(視需求而定)。 |
| 借用規則導致無法同時取得可變與不可變迭代器 | 同一作用域內不可同時持有 &mut 與 &。 |
把迭代分成不同程式區塊,或使用 split_at_mut 取得兩段可變切片。 |
for 迴圈內改變集合長度 |
在迭代時 push/remove 會導致 panic。 |
使用 while let Some(item) = vec.pop() 或先收集到新容器再處理。 |
忘記使用 & 解引用 |
iter_mut 取得的是 &mut T,直接使用會編譯錯誤。 |
使用 *item 或 item.clone()(若需要所有權)。 |
過度使用 clone() |
為了取得所有權而大量 clone,會降低效能。 |
優先考慮 into_iter、mem::replace、或 Option::take。 |
最佳實踐
- 盡量使用不可變迭代 (
iter):保持資料不被意外修改,編譯器會給予最好的優化。 - 在需要修改時使用
iter_mut,但僅在確定不會同時持有其他借用的情況下使用。 - 對於大量資料搬移,使用
into_iter+collect,可一次性取得所有權,避免多次clone。 - 善用適配器鏈:一次完成過濾、映射、累積,讓程式碼更具表達力且易於維護。
- 測試與基準:對於關鍵迭代路徑,使用
cargo bench或criterion觀察效能差異,選擇最適合的遍歷方式。
實際應用場景
| 場景 | 可能的集合型別 | 建議的遍歷方式 |
|---|---|---|
| 資料庫查詢結果分頁 | Vec<Record> |
iter().skip(page*size).take(size) |
| 統計字頻 | HashMap<String, usize> |
`iter().map( |
| 圖形渲染的頂點緩衝 | Vec<Vertex> |
iter_mut() 直接修改座標或顏色 |
| 多執行緒任務分配 | Vec<Job> |
`into_iter().enumerate().map( |
| 串流資料過濾 | Vec<u8>(或 Iterator<Item=u8>) |
`filter( |
範例:字頻統計
use std::collections::HashMap;
fn word_count(text: &str) -> HashMap<&str, usize> {
let mut map = HashMap::new();
for word in text.split_whitespace() {
*map.entry(word).or_insert(0) += 1;
}
map
}
fn main() {
let paragraph = "rust rust rust is fast and safe rust";
let freq = word_count(paragraph);
for (word, count) in freq.iter() {
println!("{} 出現了 {} 次", word, count);
}
}
此例使用 HashMap::entry 搭配 不可變迭代,在保證安全的同時完成高效統計。
總結
集合的 迭代與遍歷 是 Rust 程式設計的基礎,也是寫出安全且高效程式的關鍵。透過 iter、iter_mut、into_iter 三種取得方式,我們可以在不同的所有權需求下選擇最合適的迭代器;再配合豐富的 適配器(map、filter、enumerate、fold…),即可在單行表達式中完成複雜的資料處理。
在實務開發中,記得遵守所有權與借用規則、避免在迭代過程中改變集合結構,並善用 最佳實踐(盡量使用不可變迭代、適時搬移所有權)。只要掌握這些概念,你就能在 Rust 中自如地操作 Vec、HashMap、HashSet 等集合,寫出既簡潔又效能佳的程式碼。
祝你在 Rust 的集合世界裡玩得開心,持續探索更高階的迭代技巧吧! 🚀