快轉到主要內容
  1. 教學文章/

Rust Collections vs Python:Vec 和 HashMap 的生存指南

·7 分鐘· loading · loading · ·
Rust Python Collections Vec Hashmap
每日拍拍
作者
每日拍拍
科學家 X 科技宅宅
目錄
Rust 入門 - 本文屬於一個選集。
§ 8: 本文

一、前言
#

嗨,我是拍拍君 🦀

寫 Python 的時候,listdict 大概是你每天用最多的兩個資料結構。隨便塞什麼進去都行,混著放也沒問題——一個 list 裡可以同時有 intstr、甚至另一個 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 模組不只有 VecHashMap,還有好幾個實用的集合。簡單介紹一下:

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>(最大堆)

七、實戰練習:學生成績統計
#

讓我們用一個完整的例子,把 VecHashMap 結合起來:

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 更靈活

🔹 迭代器整合filtermapcollect 的鏈式操作超優雅,而且是零成本抽象

🔹 多樣選擇BTreeMapHashSetVecDeque……根據需求選擇最適合的集合

如果你是 Python 工程師,學 Rust 集合最大的挑戰不是語法——而是理解所有權如何影響集合操作。一旦搞懂了,你會發現 Rust 的集合其實非常直覺,而且編譯器會當你最好的朋友,幫你抓出所有潛在問題。

下一篇我們會繼續探索 Rust 的世界,敬請期待!🦀


延伸閱讀
#

Rust 入門 - 本文屬於一個選集。
§ 8: 本文

相關文章

Rust 生命週期(Lifetime)入門:編譯器教你管記憶體
·8 分鐘· loading · loading
Rust Lifetime Borrowing Memory-Safety Python
Rust 錯誤處理:Result/Option vs Python try/except
·7 分鐘· loading · loading
Rust Python Error-Handling Result Option
Rust for Python 開發者:Ownership 與 Borrowing 入門
·7 分鐘· loading · loading
Rust Python Ownership Borrowing Memory
Rust CLI 實戰:用 clap 打造命令列工具(Python Typer 對照版)
·5 分鐘· loading · loading
Rust Cli Clap Typer Python
Rust Ownership & Borrowing:跟 Python GC 說再見
·8 分鐘· loading · loading
Rust Ownership Borrowing Python 記憶體管理
Rust trait vs Python ABC/Protocol:抽象介面大比拼
·8 分鐘· loading · loading
Rust Python Trait Abc Protocol 泛型 Interface