為什麼要一個無垃圾回收卻記憶體安全的系統語言(Rust 的動機)
/ Rust -> rust /
假設你已經學過 C,也見識過它的尖銳邊緣:記憶體釋放後仍被使用的指標、寫超出緩衝區一個位元組、兩段程式碼都釋放同一塊區塊。這些都不是什麼罕見狀況——它們是日常的失誤,而在 C 裡編譯器會直接放行。常見的出路是改用帶垃圾回收(garbage collector)的語言(如 Java、Go 或 Python),由它替你管理記憶體,讓你無法犯下那些錯。但垃圾回收器是在你程式執行時一起跑的:它會週期性地暫停下來搜尋未使用的記憶體,並加上一份你無法掌控的執行期成本。對作業系統核心、遊戲引擎或裝置驅動程式而言,那些暫停與額外負擔可能完全無法接受。於是數十年來這筆交換看似無可迴避:要嘛是 C 的手動掌控與速度(以及它一整族的記憶體錯誤),要嘛是垃圾回收語言的安全(以及它的執行期成本)。Rust 的存在,就是為了拒絕這筆交換。
Rust 是一種系統程式設計語言——它編譯成原生機器碼、直接與硬體對話、底下沒有必需的執行期、並給你和 C 一樣的低階掌控(你仍要思考堆疊、堆積、以及位元組住在哪裡)。新的地方在於:Rust 把記憶體安全變成一項在編譯期檢查的性質,在程式跑起來之前就驗證,而且不靠垃圾回收。編譯器會在你寫程式碼時追蹤每塊記憶體歸誰所有、誰被允許讀寫它、以及它究竟何時被釋放。若你的程式碼可能讀到已釋放的記憶體、寫超出緩衝區結尾、或讓兩條執行緒競爭同一份資料,程式就單純地無法編譯——你得到的是一個錯誤,而不是三週後在正式環境裡的當機。這一切的代價,是由程式設計師在編譯期支付(學會規則、滿足檢查器),而非由程式在執行期支付。
對這個主張要誠實,因為這份誠實正是重點。Rust 並沒有讓錯誤變得不可能。它在以安全子集寫成的程式碼裡,防止一個特定而重要的錯誤家族——釋放後使用、重複釋放、懸置指標、緩衝區越界、資料競爭。它不能阻止邏輯錯誤、不能阻止你故意洩漏記憶體、不能阻止死結;而當你一踏進 unsafe 區塊(有時你必須如此,為了與硬體或 C 對話),你就回到了類似 C 的責任之中。最好別把 Rust 理解為魔法,而要理解為:它把優秀 C 程式設計師早已記在腦中的所有權紀律,編碼進語言裡,然後強迫每個人都遵守。這個領域接下來的內容,就在拆解它是怎麼做到的。
// C:這會編譯通過,並可能無聲地當機、洩漏或損毀資料。 char *p = malloc(8); free(p); puts(p); // 釋放後使用;C 什麼也不說 // Rust:同樣的失誤根本無法編譯。 let p = String::from("hi"); drop(p); // 值在此被釋放 println!("{p}"); // error[E0382]:使用已移動的值 `p`
同一個錯,兩種語言:C 把它編譯出來然後祈禱;Rust 根本拒絕編譯。
無 GC 卻記憶體安全是它的招牌,但這份安全保證只涵蓋安全的 Rust。在 unsafe 區塊內,或跨越外部函式邊界進入 C 時,你仍可能撞上和 C 一模一樣的未定義行為。