本文 AI 產出,尚未審核

Rust 集合型別:集合的迭代與遍歷


簡介

在日常開發中,**集合(Collection)**是最常使用的資料結構之一。無論是儲存使用者輸入、處理檔案內容,或是實作演算法,都離不開 Vec<T>HashMap<K, V>HashSet<T> 等容器。Rust 為了在保證安全性的同時提供高效能,對集合的 迭代(iteration)遍歷(traversal) 加入了許多語言層面的支援與最佳化。

本單元將帶你了解:

  1. 為什麼迭代是 Rust 中最常見且最安全的資料存取方式。
  2. 如何使用不同的迭代器(iteriter_mutinto_iter)以及常見的 適配器(adapter)(如 mapfilterenumerate)。
  3. 在實務開發中避免常見的所有權與借用陷阱,寫出既簡潔高效的遍歷程式碼。

核心概念

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:iterfor 迴圈

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,直接使用會編譯錯誤。 使用 *itemitem.clone()(若需要所有權)。
過度使用 clone() 為了取得所有權而大量 clone,會降低效能。 優先考慮 into_itermem::replace、或 Option::take

最佳實踐

  1. 盡量使用不可變迭代 (iter):保持資料不被意外修改,編譯器會給予最好的優化。
  2. 在需要修改時使用 iter_mut,但僅在確定不會同時持有其他借用的情況下使用。
  3. 對於大量資料搬移,使用 into_iter + collect,可一次性取得所有權,避免多次 clone
  4. 善用適配器鏈:一次完成過濾、映射、累積,讓程式碼更具表達力且易於維護。
  5. 測試與基準:對於關鍵迭代路徑,使用 cargo benchcriterion 觀察效能差異,選擇最適合的遍歷方式。

實際應用場景

場景 可能的集合型別 建議的遍歷方式
資料庫查詢結果分頁 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 程式設計的基礎,也是寫出安全且高效程式的關鍵。透過 iteriter_mutinto_iter 三種取得方式,我們可以在不同的所有權需求下選擇最合適的迭代器;再配合豐富的 適配器mapfilterenumeratefold…),即可在單行表達式中完成複雜的資料處理。

在實務開發中,記得遵守所有權與借用規則、避免在迭代過程中改變集合結構,並善用 最佳實踐(盡量使用不可變迭代、適時搬移所有權)。只要掌握這些概念,你就能在 Rust 中自如地操作 VecHashMapHashSet 等集合,寫出既簡潔效能佳的程式碼。

祝你在 Rust 的集合世界裡玩得開心,持續探索更高階的迭代技巧吧! 🚀