5.1 為什麼選擇 Rust?
5.1 為什麼選擇 Rust?
本章比較 Rust 與傳統 C/C++ 作為 Python 擴展語言的優缺點。
本章目標
學完本章後,你將能夠:
- 理解 Rust 的記憶體安全保證
- 評估 Rust vs C/C++ 的取捨
- 認識使用 Rust 的知名 Python 專案
【原理層】Rust 的核心優勢
記憶體安全
Rust 透過所有權(Ownership)系統在編譯時保證記憶體安全:
1// Rust 的所有權規則
2fn main() {
3 let s1 = String::from("hello");
4 let s2 = s1; // s1 的所有權轉移給 s2
5
6 // println!("{}", s1); // 編譯錯誤!s1 已經無效
7 println!("{}", s2); // OK
8}
9
10// 借用(Borrowing)
11fn calculate_length(s: &String) -> usize {
12 s.len()
13} // s 離開作用域,但不會釋放記憶體(只是借用)
14
15fn main() {
16 let s = String::from("hello");
17 let len = calculate_length(&s); // 借用 s
18 println!("長度: {}, 字串: {}", len, s); // s 仍然有效
19}與 C/C++ 的對比:
1// C 語言:常見的記憶體錯誤
2
3// 1. Use After Free
4char* ptr = malloc(100);
5free(ptr);
6printf("%s", ptr); // 未定義行為!
7
8// 2. Double Free
9char* ptr = malloc(100);
10free(ptr);
11free(ptr); // 未定義行為!
12
13// 3. Buffer Overflow
14char buffer[10];
15strcpy(buffer, "This string is way too long!"); // 溢位!
16
17// 4. Null Pointer Dereference
18char* ptr = NULL;
19printf("%c", *ptr); // Crash!
1// Rust:上述錯誤在編譯時就會被捕捉
2
3// 1. Use After Free - 不可能
4let s = String::from("hello");
5drop(s); // 明確釋放
6// println!("{}", s); // 編譯錯誤:value borrowed after move
7
8// 2. Double Free - 不可能
9// 所有權系統確保每個值只會被釋放一次
10
11// 3. Buffer Overflow - 執行時檢查
12let v = vec![1, 2, 3];
13// v[10]; // 執行時 panic,不是未定義行為
14
15// 4. Null Pointer - 使用 Option 類型
16let maybe_value: Option<i32> = None;
17// 必須明確處理 None 的情況
18match maybe_value {
19 Some(v) => println!("值: {}", v),
20 None => println!("沒有值"),
21}並行安全
Rust 的類型系統在編譯時防止資料競爭:
1use std::thread;
2
3// 編譯錯誤:不能在多個執行緒中修改同一資料
4fn wont_compile() {
5 let mut data = vec![1, 2, 3];
6
7 thread::spawn(|| {
8 data.push(4); // 錯誤:無法借用
9 });
10
11 data.push(5);
12}
13
14// 正確:使用 Arc 和 Mutex
15use std::sync::{Arc, Mutex};
16
17fn correct_approach() {
18 let data = Arc::new(Mutex::new(vec![1, 2, 3]));
19 let data_clone = Arc::clone(&data);
20
21 let handle = thread::spawn(move || {
22 let mut d = data_clone.lock().unwrap();
23 d.push(4);
24 });
25
26 {
27 let mut d = data.lock().unwrap();
28 d.push(5);
29 }
30
31 handle.join().unwrap();
32}零成本抽象
高階語法不會帶來執行時開銷:
1// 迭代器鏈式操作
2let sum: i32 = (0..1000)
3 .filter(|x| x % 2 == 0)
4 .map(|x| x * x)
5 .sum();
6
7// 編譯後等同於手寫的迴圈
8// 沒有額外的函式呼叫或記憶體分配
【比較】Rust vs C/C++
效能比較
1效能特性比較:
2
3 Rust C C++
4────────────────────────────────────────────────────
5執行速度 5/5 5/5 5/5
6編譯時間 2/5 4/5 3/5
7二進位大小 3/5 5/5 4/5
8記憶體使用 5/5 5/5 4/5
9
10說明:
11- 執行速度三者相當(都編譯為原生機器碼)
12- Rust 編譯時間較長(複雜的類型檢查)
13- Rust 二進位可能較大(標準庫靜態連結)開發體驗比較
1開發體驗:
2
3 Rust C C++
4────────────────────────────────────────────────────
5學習曲線 陡峭 中等 陡峭
6除錯難度 較易 困難 困難
7錯誤訊息 優秀 普通 差
8套件管理 (Cargo) 5/5 無 2/5
9IDE 支援 4/5 5/5 5/5
10
11說明:
12- Rust 的學習曲線主要來自所有權系統
13- Rust 的錯誤通常在編譯時發現,更容易修復
14- Cargo 提供了優秀的套件管理和建構系統安全性比較
1安全性:
2
3問題類型 Rust C/C++
4──────────────────────────────────────────────────────
5記憶體洩漏 可能(較少見) 常見
6Use After Free 編譯時防止 常見
7Buffer Overflow 執行時 panic 未定義行為
8Null Pointer 使用 Option 常見 crash
9Data Race 編譯時防止 常見
10未初始化變數 編譯時防止 可能
11
12Rust 的 unsafe:
13- 可以繞過某些安全檢查
14- 明確標記,易於審查
15- 應該盡量減少使用【案例】使用 Rust 的知名 Python 專案
tiktoken(OpenAI)
1tiktoken - OpenAI 的 tokenizer
2
3用途:
4- GPT 模型的 token 編碼/解碼
5- 處理大量文字資料
6
7為什麼選擇 Rust:
8- 效能要求高(處理大量請求)
9- 需要處理各種邊界情況
10- 記憶體安全很重要
11
12效能數據:
13- 比純 Python 實現快 3-10x
14- 與 C++ 實現效能相當tokenizers(Hugging Face)
1tokenizers - Hugging Face 的 tokenizer 函式庫
2
3用途:
4- NLP 模型的 token 處理
5- 支援多種 tokenization 演算法
6
7特點:
8- 純 Rust 核心
9- PyO3 提供 Python 綁定
10- 支援多種語言綁定(Node.js、Ruby 等)
11
12為什麼選擇 Rust:
13- 需要高效能的字串處理
14- 需要安全地處理任意輸入
15- 跨平台支援Polars
1Polars - 高效能 DataFrame 函式庫
2
3用途:
4- pandas 的替代方案
5- 大規模資料處理
6
7特點:
8- 比 pandas 快 10-100x(某些操作)
9- 惰性求值
10- 多執行緒
11
12為什麼選擇 Rust:
13- 需要最佳效能
14- 需要安全的並行處理
15- Arrow 生態系統整合
16
17使用範例:
18import polars as pl
19
20df = pl.read_csv("large_file.csv")
21result = (
22 df.lazy()
23 .filter(pl.col("value") > 100)
24 .group_by("category")
25 .agg(pl.col("value").sum())
26 .collect()
27)Ruff
1Ruff - 超快的 Python linter
2
3用途:
4- Python 程式碼檢查
5- 取代 flake8、isort、pyupgrade 等
6
7特點:
8- 比 flake8 快 10-100x
9- 單一工具取代多個 linter
10
11為什麼選擇 Rust:
12- 需要處理大型程式碼庫
13- 需要快速的回饋(IDE 整合)
14- 字串處理效能關鍵
15
16效能數據:
17- CPython 程式碼庫(約 60 萬行)
18- Ruff: 0.29 秒
19- flake8: 22 秒pydantic-core
1pydantic-core - Pydantic v2 的核心
2
3用途:
4- 資料驗證和序列化
5- 廣泛用於 FastAPI
6
7特點:
8- Pydantic v2 比 v1 快 5-50x
9- 核心驗證邏輯用 Rust 重寫
10
11為什麼選擇 Rust:
12- 驗證邏輯被頻繁呼叫
13- 需要處理各種邊界情況
14- 需要安全地處理任意輸入【評估】何時選擇 Rust
適合使用 Rust 的場景
1應該考慮 Rust:
2
31. 效能關鍵的程式碼
4 - 大量數值計算
5 - 頻繁呼叫的函式
6 - 處理大型資料集
7
82. 需要安全地處理任意輸入
9 - 解析使用者提供的資料
10 - 網路協議處理
11 - 檔案格式解析
12
133. 需要並行處理
14 - 多執行緒計算
15 - 非同步 I/O
16 - 需要避免 GIL
17
184. 長期維護的核心函式庫
19 - 減少記憶體相關 bug
20 - 更容易重構
21 - 跨語言共享
22
235. 跨平台發布
24 - Rust 交叉編譯支援好
25 - 減少平台特定 bug不太適合 Rust 的場景
1可能不需要 Rust:
2
31. 快速原型開發
4 - Python 本身就夠用
5 - 學習成本高
6
72. 已有 C/C++ 程式碼
8 - 直接用 pybind11 包裝
9 - 除非需要大幅修改
10
113. 團隊不熟悉 Rust
12 - 學習曲線陡峭
13 - 維護成本考量
14
154. 效能不是瓶頸
16 - 先用 profiler 確認
17 - 可能 Python 優化就夠了
18
195. 只需要呼叫現有 C 函式庫
20 - ctypes/cffi 更簡單
21 - 不需要額外語言決策流程
1需要 Python 擴展?
2│
3├── 有現有 C/C++ 程式碼?
4│ ├── 是 → pybind11/Cython
5│ └── 否 ↓
6│
7├── 效能是主要考量?
8│ ├── 否 → 純 Python 或 Cython
9│ └── 是 ↓
10│
11├── 需要並行安全?
12│ ├── 是 → Rust (PyO3)
13│ └── 否 ↓
14│
15├── 團隊熟悉 Rust?
16│ ├── 是 → Rust (PyO3)
17│ └── 否 → Cython 或學習 Rust
18│
19└── 長期維護的核心程式碼?
20 ├── 是 → 考慮 Rust
21 └── 否 → Cython 可能更實際【入門】Rust 基礎概念
給 Python 開發者的快速入門
1// 變數與型別
2fn basics() {
3 // 不可變變數(預設)
4 let x = 5;
5 // x = 6; // 錯誤!
6
7 // 可變變數
8 let mut y = 5;
9 y = 6; // OK
10
11 // 型別推斷
12 let z = 10; // i32
13 let pi = 3.14; // f64
14
15 // 明確型別
16 let a: i64 = 100;
17 let b: f32 = 3.14;
18}
19
20// 函式
21fn add(a: i32, b: i32) -> i32 {
22 a + b // 沒有分號 = 回傳值
23}
24
25// 結構體(類似 Python class)
26struct Point {
27 x: f64,
28 y: f64,
29}
30
31impl Point {
32 // 關聯函式(類似 classmethod)
33 fn new(x: f64, y: f64) -> Self {
34 Point { x, y }
35 }
36
37 // 方法
38 fn distance(&self, other: &Point) -> f64 {
39 let dx = self.x - other.x;
40 let dy = self.y - other.y;
41 (dx * dx + dy * dy).sqrt()
42 }
43}
44
45// 列舉(比 Python enum 更強大)
46enum Option<T> {
47 Some(T),
48 None,
49}
50
51enum Result<T, E> {
52 Ok(T),
53 Err(E),
54}
55
56// 模式匹配
57fn handle_option(opt: Option<i32>) {
58 match opt {
59 Some(value) => println!("有值: {}", value),
60 None => println!("沒有值"),
61 }
62}
63
64// 迭代器(類似 Python generator)
65fn iterators() {
66 let v = vec![1, 2, 3, 4, 5];
67
68 // 類似 Python 的 list comprehension
69 let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect();
70
71 // 類似 Python 的 filter
72 let evens: Vec<&i32> = v.iter().filter(|x| *x % 2 == 0).collect();
73}Python vs Rust 語法對照
1Python Rust
2──────────────────────────────────────────────────────
3x = 5 let x = 5;
4x = 5 # mutable let mut x = 5;
5def foo(x): fn foo(x: i32) -> i32 {
6 return x + 1 x + 1
7 }
8class Point: struct Point {
9 def __init__(self, x, y): x: f64,
10 self.x = x y: f64,
11 self.y = y }
12[x*2 for x in lst] lst.iter().map(|x| x*2).collect()
13if x is None: if x.is_none() {
14 ... ...
15for i in range(10): for i in 0..10 {
16 ... ...
17try: ... except: match result {
18 ... Ok(v) => ...,
19 Err(e) => ...,
20 }思考題
- Rust 的所有權系統如何與 Python 的垃圾回收協調?PyO3 如何處理這個問題?
- 為什麼許多高效能 Python 函式庫選擇用 Rust 重寫而不是 C++?
- 學習 Rust 對 Python 開發者有什麼長期價值?
實作練習
- 安裝 Rust 並完成官方教學「rustlings」的前 20 個練習
- 比較用 Python、Cython 和 Rust 實現 Fibonacci 函式的效能
- 研究一個用 Rust 寫的 Python 函式庫(如 Polars),理解其專案結構
延伸閱讀
- The Rust Programming Language
- Rust by Example
- PyO3 User Guide
- Are We Fast Yet? - Rust Performance Benchmarks
下一章:PyO3 基礎