2019 年,Chrome 瀏覽器的安全報告指出:70% 的嚴重安全漏洞是記憶體安全問題

Microsoft 對自己產品的分析得出了類似的數字:CVE 裡面,70% 是記憶體相關的 bug。

這些 bug 的根本原因:C/C++ 的手動記憶體管理。Use-after-free、buffer overflow、null pointer dereference——這些問題幾十年來始終存在,因為 C/C++ 沒有機制在語言層面阻止它們。

Rust 的 borrow checker 是第一個在不犧牲 C 的效能(無 GC、無 runtime)的前提下,在編譯時保證這些問題不存在的系統。


Ownership:記憶體生命週期的明確所有權

Rust 的核心規則:每個值有且只有一個 owner,owner 離開 scope,值被釋放。

fn main() {
    let s1 = String::from("hello");  // s1 是 owner
    let s2 = s1;                     // ownership 轉移給 s2
    // println!("{}", s1);           // 編譯錯誤:s1 已經沒有 ownership
    println!("{}", s2);              // OK
}  // s2 離開 scope,"hello" 被釋放

這個規則消除了 double-free(同一塊記憶體被釋放兩次):

// C:危險
char *s1 = malloc(100);
char *s2 = s1;  // 兩個指標指向同一塊記憶體
free(s1);
free(s2);  // double-free:undefined behavior

Rust 不讓你有兩個 owner,所以不可能 double-free。


Borrowing:不轉移 ownership 的引用

大多數情況你不想轉移 ownership,只想「借用」值:

fn print_length(s: &String) {  // 借用 String,不取 ownership
    println!("length: {}", s.len());
}
 
fn main() {
    let s = String::from("hello");
    print_length(&s);    // 借用
    println!("{}", s);   // s 還在,因為 print_length 只借用
}

借用規則

  • 任何時候,你可以有任意多個不可變引用&T
  • 或者你可以有一個可變引用&mut T
  • 這兩種不能同時存在
let mut v = vec![1, 2, 3];
let first = &v[0];     // 不可變借用
v.push(4);             // 編譯錯誤:v 被不可變借用中,不能可變修改
println!("{}", first);

這個規則消除了 data race:不可能有兩個 goroutine/thread 同時讀寫同一個值,因為「有一個可變引用」和「有任何其他引用」不能同時成立。


Lifetime:引用的有效範圍

Rust 的 borrow checker 追蹤每個引用的 lifetime,確保引用不會 outlive 它指向的值(use-after-free):

fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    if s1.len() > s2.len() { s1 } else { s2 }
}

'a 是 lifetime annotation——說明「回傳的引用和輸入引用有一樣長的 lifetime」。如果你嘗試回傳一個指向函式內部 local 變數的引用,borrow checker 拒絕編譯:

fn dangling() -> &str {
    let s = String::from("hello");
    &s  // 編譯錯誤:s 在函式結束時被 drop,引用將懸空(dangling reference)
}

C 裡面這個錯誤會靜默通過,在 runtime 造成 use-after-free。Rust 在編譯時就拒絕。


這對後端工程師意味著什麼

即使你不寫 Rust,理解這些概念也有用

1. 更清晰地思考 Go 的並行安全

Go 沒有 borrow checker,但 race detector(go test -race)能在 runtime 抓到 data race。理解 Rust 的「為什麼不能同時有可變和不可變引用」,你就更清楚 Go 裡哪些操作需要加鎖:

// Go:這是 data race,borrow checker 不幫你,你要自己知道
var counter int
go func() { counter++ }()
go func() { fmt.Println(counter) }()  // 讀寫同時發生,race condition

2. 理解 API 設計裡的所有權語意

即使在 Python 或 Go,你在設計 API 時也在隱性地做 ownership 決策:

# Python:這個 list 的 ownership 是誰的?
def process(items: list[str]) -> None:
    items.append("processed")  # 你在修改呼叫方的 list
 
# 更清楚的設計:明確語意
def process(items: list[str]) -> list[str]:
    return items + ["processed"]  # 返回新的 list,不修改原始

「函式是否修改輸入」「回傳的物件誰負責釋放」——這些在 Rust 裡是型別系統強制的問題,在其他語言裡是你要在文件或 code review 裡明確的問題。

3. Rust 在後端的實際位置

Rust 不是要取代 Go 或 Python 的業務邏輯層,而是在幾個特定場景有明顯優勢:

WebAssembly

Rust 是 WASM 的一等公民,可以編譯成 WASM 在瀏覽器或 edge runtime(Cloudflare Workers)跑,效能接近 native。

效能關鍵路徑

資料壓縮、密碼學、高頻序列化/反序列化——這些在 Go 或 Python 可能已經是瓶頸的操作,Rust 能做到接近 C 的效能,同時沒有手動記憶體管理的安全風險。

系統工具

CLI、daemon、proxy——Ripgrep(比 grep 快)、exa(ls 替代品)、Starship(shell prompt)——這個類別的工具大量在用 Rust。

FFI(Foreign Function Interface)

當你的 Python service 需要呼叫一個高效能的 native 函式,Rust 寫的 Python extension(PyO3)比 C extension 更安全、更容易維護。


Borrow Checker 的學習曲線是真實的

不要低估 borrow checker 的學習難度。初學者常見的模式:

  1. 寫一段 Rust code
  2. borrow checker 報錯
  3. 加一個 .clone()(複製資料)讓它通過
  4. 效能沒問題,但沒有真正理解為什麼

這是可以接受的起點。真正的掌握是:能在腦子裡預見 borrow checker 的問題,在設計時就選擇正確的所有權結構

到達這個層次需要幾個月的實際使用,不是讀文件能解決的。但 borrow checker 報的每一個錯,都是在教你一個在其他語言裡可能是 runtime bug 的問題——這個學習密度,是 Rust 社群認為值得代價的原因。

B01 完整文章列表