一、前言 #
嗨,我是拍拍君 🦀
寫 Python 的時候,list 和 dict 大概是你每天用最多的兩個資料結構。隨便塞什麼進去都行,混著放也沒問題——一個 list 裡可以同時有 int、str、甚至另一個 list,Python 完全不會阻止你。
# Python:什麼都能塞,我最自由
chaos = [42, "hello", [1, 2, 3], {"key": "value"}, None]
但到了 Rust 的世界,規矩就不一樣了。Vec<T> 只能放同一種型別,HashMap<K, V> 的 key 和 value 型別也必須統一。這聽起來好像很不方便?但其實這正是 Rust 的強項——在編譯期就幫你抓出型別錯誤,不用等到 runtime 才噴 TypeError。
今天我們就來把 Python 的 list / dict 跟 Rust 的 Vec / HashMap 做個完整比較,看看 Rust 的集合到底厲害在哪裡!
二、Vec:Rust 版的 list #
基本操作 #
Python 的 list 對應到 Rust 就是 Vec<T>(vector 的簡稱)。來看看基本操作的對照:
// Rust:建立 Vec
let mut numbers: Vec<i32> = Vec::new();
numbers.push(1);
numbers.push(2);
numbers.push(3);
// 或是用 vec! 巨集,更簡潔
let numbers = vec![1, 2, 3];
# Python 對照
numbers = []
numbers.append(1)
numbers.append(2)
numbers.append(3)
# 或直接
numbers = [1, 2, 3]
看起來很像對吧?差別在於 Rust 的 Vec<i32> 只能放 i32,你試圖 push 一個字串進去,編譯器會直接拒絕:
let mut numbers = vec![1, 2, 3];
numbers.push("oops"); // ❌ 編譯錯誤!expected `i32`, found `&str`
存取元素 #
let fruits = vec!["apple", "banana", "cherry"];
// 方法 1:直接索引(超出範圍會 panic)
let first = fruits[0]; // "apple"
// 方法 2:用 .get()(回傳 Option,安全)
match fruits.get(10) {
Some(fruit) => println!("Got: {}", fruit),
None => println!("Index out of bounds!"), // ✅ 安全處理
}
# Python 對照
fruits = ["apple", "banana", "cherry"]
first = fruits[0] # "apple"
# 超出範圍直接 IndexError
try:
fruits[10]
except IndexError:
print("Index out of bounds!")
這邊 Rust 的 .get() 方法很棒——回傳 Option<&T>,讓你不用靠 try-except 來處理邊界情況。如果你讀過我們的 Rust 錯誤處理 那篇,對 Option 應該不陌生。
常用方法對照表 #
| 操作 | Python list |
Rust Vec<T> |
|---|---|---|
| 建立空集合 | [] |
Vec::new() |
| 建立有初值 | [1, 2, 3] |
vec![1, 2, 3] |
| 加到尾端 | .append(x) |
.push(x) |
| 移除尾端 | .pop() |
.pop() |
| 長度 | len(lst) |
.len() |
| 是否為空 | not lst |
.is_empty() |
| 排序 | .sort() |
.sort() |
| 反轉 | .reverse() |
.reverse() |
| 搜尋 | x in lst |
.contains(&x) |
| 切片 | lst[1:3] |
&v[1..3] |
| 插入 | .insert(i, x) |
.insert(i, x) |
| 移除指定位置 | .pop(i) |
.remove(i) |
三、Vec 與所有權 #
這是 Rust 跟 Python 最根本的差異。Python 的 list 裡面放的都是參考(reference),所以怎麼抄怎麼傳都沒問題。但 Rust 的 Vec 遵守所有權規則——你得搞清楚誰「擁有」這些資料。
移動 vs 複製 #
let v1 = vec![1, 2, 3];
let v2 = v1; // v1 的所有權移動到 v2
// println!("{:?}", v1); // ❌ 編譯錯誤!v1 已經無效了
println!("{:?}", v2); // ✅ [1, 2, 3]
# Python:怎麼都行
v1 = [1, 2, 3]
v2 = v1 # v2 跟 v1 指向同一個 list
print(v1) # ✅ [1, 2, 3]
print(v2) # ✅ [1, 2, 3]
v2.append(4)
print(v1) # [1, 2, 3, 4] — 驚不驚喜?意不意外?
Python 那個「v1 跟 v2 指向同一個 list」的行為,對初學者來說其實是個超常見的 bug 來源。Rust 的所有權機制雖然嚴格,但反而避免了這類隱含的共享修改問題。
如果你真的需要複製,Rust 也有辦法:
let v1 = vec![1, 2, 3];
let v2 = v1.clone(); // 明確的深拷貝
println!("{:?}", v1); // ✅ [1, 2, 3]
println!("{:?}", v2); // ✅ [1, 2, 3]
借用 Vec 的元素 #
let names = vec![
String::from("Alice"),
String::from("Bob"),
String::from("Charlie"),
];
// 不可變借用:可以讀,不能改
let first = &names[0];
println!("First: {}", first); // ✅
// 如果同時有不可變借用,就不能修改 Vec
// names.push(String::from("Dave")); // ❌ 不行!first 還在借用中
為什麼 push 會被禁止?因為 push 可能觸發 Vec 的記憶體重新分配(reallocation),導致 first 指向的位置失效。Rust 編譯器保護你不會踩到 dangling pointer,這在 C++ 裡是個經典 bug。
如果你忘了借用規則,可以回去翻翻 Rust ownership 那篇。
四、HashMap:Rust 版的 dict #
Python 的 dict 對應 Rust 的 HashMap<K, V>。先引入:
use std::collections::HashMap;
沒錯,跟 Vec 不一樣,HashMap 不在 prelude 裡,需要手動 use。
基本操作 #
use std::collections::HashMap;
// 建立並插入
let mut scores: HashMap<String, i32> = HashMap::new();
scores.insert(String::from("Alice"), 95);
scores.insert(String::from("Bob"), 87);
scores.insert(String::from("Charlie"), 92);
// 讀取
if let Some(score) = scores.get("Alice") {
println!("Alice 的分數:{}", score); // 95
}
# Python 對照
scores = {}
scores["Alice"] = 95
scores["Bob"] = 87
scores["Charlie"] = 92
print(scores["Alice"]) # 95
# 或安全版
print(scores.get("Alice", "Not found"))
常用方法對照表 #
| 操作 | Python dict |
Rust HashMap<K, V> |
|---|---|---|
| 建立 | {} |
HashMap::new() |
| 插入/更新 | d[k] = v |
.insert(k, v) |
| 讀取 | d[k] |
直接索引不行,用 .get(&k) |
| 安全讀取 | d.get(k, default) |
.get(&k) 回傳 Option |
| 刪除 | del d[k] |
.remove(&k) |
| 是否有 key | k in d |
.contains_key(&k) |
| 所有 keys | d.keys() |
.keys() |
| 所有 values | d.values() |
.values() |
| 長度 | len(d) |
.len() |
| 走訪 | for k, v in d.items() |
for (k, v) in &map |
entry API:超強的 setdefault #
Python 有個 dict.setdefault() 或 collections.defaultdict。Rust 的 entry API 更強大:
use std::collections::HashMap;
let text = "hello world hello rust hello world";
let mut word_count: HashMap<&str, i32> = HashMap::new();
for word in text.split_whitespace() {
let count = word_count.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", word_count);
// {"hello": 3, "world": 2, "rust": 1}
# Python 對照
from collections import Counter
text = "hello world hello rust hello world"
word_count = Counter(text.split())
print(word_count)
# Counter({'hello': 3, 'world': 2, 'rust': 1})
Python 的 Counter 當然很方便,但 Rust 的 entry API 更通用——你可以在 or_insert_with 裡放任何初始化邏輯:
use std::collections::HashMap;
let mut groups: HashMap<&str, Vec<i32>> = HashMap::new();
let data = vec![("fruit", 1), ("veggie", 2), ("fruit", 3), ("veggie", 4)];
for (category, value) in data {
groups.entry(category).or_insert_with(Vec::new).push(value);
}
println!("{:?}", groups);
// {"fruit": [1, 3], "veggie": [2, 4]}
# Python 對照
from collections import defaultdict
groups = defaultdict(list)
data = [("fruit", 1), ("veggie", 2), ("fruit", 3), ("veggie", 4)]
for category, value in data:
groups[category].append(value)
print(dict(groups))
# {'fruit': [1, 3], 'veggie': [2, 4]}
五、迭代器整合 #
Rust 的集合跟迭代器(iterator)的整合是一級棒的。如果你看過 Python itertools 那篇,Rust 的迭代器功能其實更強——因為它在編譯期就能優化,達到「零成本抽象」(zero-cost abstraction)。
函數式風格操作 #
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// filter + map + collect
let result: Vec<i32> = numbers
.iter()
.filter(|&&x| x % 2 == 0) // 偶數
.map(|&x| x * x) // 平方
.collect(); // 收集成 Vec
println!("{:?}", result); // [4, 16, 36, 64, 100]
# Python 對照
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# list comprehension(更 Pythonic)
result = [x * x for x in numbers if x % 2 == 0]
print(result) # [4, 16, 36, 64, 100]
# 或 filter + map 風格
result = list(map(lambda x: x * x, filter(lambda x: x % 2 == 0, numbers)))
從迭代器建立 HashMap #
use std::collections::HashMap;
let names = vec!["Alice", "Bob", "Charlie"];
let scores = vec![95, 87, 92];
// zip + collect 直接變 HashMap
let score_map: HashMap<&str, i32> = names
.iter()
.copied()
.zip(scores.iter().copied())
.collect();
println!("{:?}", score_map);
// {"Alice": 95, "Bob": 87, "Charlie": 92}
# Python 對照
names = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]
score_map = dict(zip(names, scores))
print(score_map)
# {'Alice': 95, 'Bob': 87, 'Charlie': 92}
看起來幾乎一樣!Rust 的 .collect() 非常聰明,能根據你指定的回傳型別自動推斷要怎麼收集。
實用的迭代器方法 #
let data = vec![10, 20, 30, 40, 50];
// 總和
let sum: i32 = data.iter().sum(); // 150
// 最大值
let max = data.iter().max(); // Some(50)
// 是否全部符合條件
let all_positive = data.iter().all(|&x| x > 0); // true
// 是否有任一符合
let has_big = data.iter().any(|&x| x > 100); // false
// 找到第一個符合的
let first_over_25 = data.iter().find(|&&x| x > 25); // Some(30)
// enumerate(跟 Python 的 enumerate 一樣!)
for (i, val) in data.iter().enumerate() {
println!("[{}] = {}", i, val);
}
六、其他常用集合 #
Rust 的 std::collections 模組不只有 Vec 和 HashMap,還有好幾個實用的集合。簡單介紹一下:
HashSet:不重複集合 #
對應 Python 的 set:
use std::collections::HashSet;
let mut fruits: HashSet<&str> = HashSet::new();
fruits.insert("apple");
fruits.insert("banana");
fruits.insert("apple"); // 重複,不會加入
println!("{}", fruits.len()); // 2
// 集合運算
let tropical: HashSet<&str> = ["banana", "mango", "papaya"].iter().copied().collect();
// 交集
let both: HashSet<_> = fruits.intersection(&tropical).collect();
println!("{:?}", both); // {"banana"}
// 聯集
let all: HashSet<_> = fruits.union(&tropical).collect();
println!("{:?}", all); // {"apple", "banana", "mango", "papaya"}
# Python 對照
fruits = {"apple", "banana", "apple"} # {'apple', 'banana'}
tropical = {"banana", "mango", "papaya"}
print(fruits & tropical) # {'banana'}
print(fruits | tropical) # {'apple', 'banana', 'mango', 'papaya'}
VecDeque:雙端佇列 #
對應 Python 的 collections.deque:
use std::collections::VecDeque;
let mut queue: VecDeque<&str> = VecDeque::new();
queue.push_back("first");
queue.push_back("second");
queue.push_front("zero");
println!("{:?}", queue); // ["zero", "first", "second"]
let front = queue.pop_front(); // Some("zero")
let back = queue.pop_back(); // Some("second")
BTreeMap:有序的 HashMap #
Python 3.7+ 的 dict 保證插入順序,但如果你要按 key 排序,就需要 sorted(d.items())。Rust 的 BTreeMap 天生就是按 key 排序的:
use std::collections::BTreeMap;
let mut map = BTreeMap::new();
map.insert("cherry", 3);
map.insert("apple", 1);
map.insert("banana", 2);
// 自動按 key 排序
for (k, v) in &map {
println!("{}: {}", k, v);
}
// apple: 1
// banana: 2
// cherry: 3
集合型別速查 #
| 需求 | Python | Rust |
|---|---|---|
| 動態陣列 | list |
Vec<T> |
| 鍵值對 | dict |
HashMap<K, V> |
| 有序鍵值對 | dict(3.7+) |
BTreeMap<K, V> |
| 不重複集合 | set |
HashSet<T> |
| 有序集合 | sorted(set(...)) |
BTreeSet<T> |
| 雙端佇列 | collections.deque |
VecDeque<T> |
| 堆積(最小堆) | heapq |
BinaryHeap<T>(最大堆) |
七、實戰練習:學生成績統計 #
讓我們用一個完整的例子,把 Vec 和 HashMap 結合起來:
use std::collections::HashMap;
fn main() {
// 學生成績資料
let records = vec![
("Alice", "Math", 95),
("Bob", "Math", 87),
("Alice", "Science", 88),
("Bob", "Science", 92),
("Charlie", "Math", 78),
("Charlie", "Science", 95),
("Alice", "English", 91),
("Bob", "English", 85),
("Charlie", "English", 89),
];
// 統計每個學生的平均分數
let mut student_scores: HashMap<&str, Vec<i32>> = HashMap::new();
for (name, _subject, score) in &records {
student_scores
.entry(name)
.or_insert_with(Vec::new)
.push(*score);
}
// 計算平均
println!("=== 學生平均成績 ===");
for (name, scores) in &student_scores {
let avg: f64 = scores.iter().sum::<i32>() as f64 / scores.len() as f64;
println!("{}: {:.1}", name, avg);
}
// 找出每科最高分
let mut subject_best: HashMap<&str, (&str, i32)> = HashMap::new();
for (name, subject, score) in &records {
subject_best
.entry(subject)
.and_modify(|(best_name, best_score)| {
if score > best_score {
*best_name = name;
*best_score = *score;
}
})
.or_insert((name, *score));
}
println!("\n=== 各科最高分 ===");
for (subject, (name, score)) in &subject_best {
println!("{}: {} ({}分)", subject, name, score);
}
}
# Python 對照
from collections import defaultdict
records = [
("Alice", "Math", 95),
("Bob", "Math", 87),
("Alice", "Science", 88),
("Bob", "Science", 92),
("Charlie", "Math", 78),
("Charlie", "Science", 95),
("Alice", "English", 91),
("Bob", "English", 85),
("Charlie", "English", 89),
]
# 每個學生的平均分
student_scores = defaultdict(list)
for name, _, score in records:
student_scores[name].append(score)
print("=== 學生平均成績 ===")
for name, scores in student_scores.items():
print(f"{name}: {sum(scores) / len(scores):.1f}")
# 各科最高分
subject_best = {}
for name, subject, score in records:
if subject not in subject_best or score > subject_best[subject][1]:
subject_best[subject] = (name, score)
print("\n=== 各科最高分 ===")
for subject, (name, score) in subject_best.items():
print(f"{subject}: {name} ({score}分)")
注意 Rust 版本用了 and_modify + or_insert 的組合——這是 entry API 的進階用法,讓你可以根據 key 是否存在來做不同的操作。
結語 #
來快速整理一下 Rust 集合 vs Python 集合的重點:
🔹 型別安全:Rust 的集合在編譯期就確定元素型別,不會出現 runtime 的 TypeError
🔹 所有權:Vec 的元素遵守所有權規則,不會有 Python 那種「兩個變數指向同一個 list」的陷阱
🔹 entry API:Rust 的 HashMap entry 比 Python 的 setdefault / defaultdict 更靈活
🔹 迭代器整合:filter、map、collect 的鏈式操作超優雅,而且是零成本抽象
🔹 多樣選擇:BTreeMap、HashSet、VecDeque……根據需求選擇最適合的集合
如果你是 Python 工程師,學 Rust 集合最大的挑戰不是語法——而是理解所有權如何影響集合操作。一旦搞懂了,你會發現 Rust 的集合其實非常直覺,而且編譯器會當你最好的朋友,幫你抓出所有潛在問題。
下一篇我們會繼續探索 Rust 的世界,敬請期待!🦀
延伸閱讀 #
- Rust 官方文件:Collections
- Rust By Example: Vectors
- Rust 所有權入門 — 搞懂 ownership 才能用好集合
- Python collections 模組 — Python 的進階集合