本章比較 Rust 與傳統 C/C++ 作為 Python 擴展語言的優缺點。

本章目標

學完本章後,你將能夠:

  1. 理解 Rust 的記憶體安全保證
  2. 評估 Rust vs C/C++ 的取捨
  3. 認識使用 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│   └── 是 ↓
1011├── 需要並行安全?
12│   ├── 是 → Rust (PyO3)
13│   └── 否 ↓
1415├── 團隊熟悉 Rust?
16│   ├── 是 → Rust (PyO3)
17│   └── 否 → Cython 或學習 Rust
1819└── 長期維護的核心程式碼?
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                                }

思考題

  1. Rust 的所有權系統如何與 Python 的垃圾回收協調?PyO3 如何處理這個問題?
  2. 為什麼許多高效能 Python 函式庫選擇用 Rust 重寫而不是 C++?
  3. 學習 Rust 對 Python 開發者有什麼長期價值?

實作練習

  1. 安裝 Rust 並完成官方教學「rustlings」的前 20 個練習
  2. 比較用 Python、Cython 和 Rust 實現 Fibonacci 函式的效能
  3. 研究一個用 Rust 寫的 Python 函式庫(如 Polars),理解其專案結構

延伸閱讀


下一章:PyO3 基礎