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 behaviorRust 不讓你有兩個 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 condition2. 理解 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 的學習難度。初學者常見的模式:
- 寫一段 Rust code
- borrow checker 報錯
- 加一個
.clone()(複製資料)讓它通過 - 效能沒問題,但沒有真正理解為什麼
這是可以接受的起點。真正的掌握是:能在腦子裡預見 borrow checker 的問題,在設計時就選擇正確的所有權結構。
到達這個層次需要幾個月的實際使用,不是讀文件能解決的。但 borrow checker 報的每一個錯,都是在教你一個在其他語言裡可能是 runtime bug 的問題——這個學習密度,是 Rust 社群認為值得代價的原因。