Ch1:為什麼是 Rust?記憶體安全的暴力美學
·
Sean Sie
Why Rust?
既然你都點進來了,原因你應該很清楚。不管是為了效能、記憶體安全,還是單純想在簡歷上看起來比較厲害,Rust 都是你的不二之選。至於細節?不知道的可以去問 ChatGPT,我們這裡只講重點。
Hello World:注意那個「!」
廢話不多說,直接看程式碼:
fn main() {
println!("hello world");
}
⚡ 極速講解
- fn main():這不用解釋吧?這是程式的起點。
- println!():這是標準輸出,但嚴格來說它是巨集 (Macro),關鍵就在那個 !。
- 為什麼要用巨集? 因為 Rust 編譯器要在編譯期就檢查你的格式化字串。普通函數做不到這種程度的靈活度,巨集能讓你在編譯階段就少掉一堆 Bug。
記憶體戰場:Stack vs Heap
Rust 對記憶體非常講究,這是它「反直覺」的來源。你必須先搞懂這兩塊地盤:
- Stack (棧):
- 這裡存「大小固定」的資料(如整數、布林值)。
- 像疊盤子,速度極快。
- Heap (堆):
- 這裡存「大小不固定」或「超巨大」的資料(如 String、動態陣列)。
- Stack 只會存下一個指向 Heap 的「指標」與長度資訊。
這就是 Rust 講究的地方,也是新手最容易撞牆的地方:Heap 的管理。
所有權 (Ownership):Rust 的靈魂
在 Rust,每個物件都必須有一個「主人」,也就是識別字。
- 規則一:每個物件只能屬於 一個 主人。
- 規則二:當你透過 = 賦值時,會根據資料存在哪而有不同反應:
- Stack 資料:預設會「複製」一份。因為資料小,Copy 一下沒負擔。
- Heap 資料:預設會「轉移所有權 (Move)」。
⚡ 轉移所有權的慘案
fn main() {
let a = String::from("rust");
let b = a;
// "rust" 的歸屬從 a 轉移到 b 了 (a ==> b)
// println!("a = {}", a); ❌ 報鎖!a 已經失去所有權,它現在是個空殼。
}
為什麼要這麼麻煩?
因為如果 a 跟 b 同時指向同一塊記憶體,當程式跑完要釋放時,這兩個人都會嘗試去刪除同一塊地。這叫「雙重釋放 (Double Free)」,是 C 語言時代的噩夢。Rust 在編譯期就直接把這種可能性掐死。
如果你真的需要兩份?請用 a.clone()。但記住,clone 會有「巨大開銷」,沒事別亂用。
借用 (Borrowing):我只是看一下
轉移所有權太暴力了,有時候我只是想借來用一下,Rust 提供了「借用」機制:
- 唯讀借用 (&foo):允許多個人同時「看」。
- 可變借用 (&mut foo):只允許一個人在那裡「改」。
金律:一寫多讀不可兼得。 當有人在改的時候,別人都不能看;大家都在看的時候,誰都不能改。
表達式導向 (Expression-Based)
Rust 是一個「表達式導向」的語言,這讓程式碼變得很簡潔。
fn hello() -> &str {
println!("executed");
"hello" // 沒加分號!這就是回傳值
}
- 有分號:語句 (Statement),執行它,但不回傳東西。
- 沒分號:表達式 (Expression),執行它,並把結果丟出去。
如果你是傳統派,Rust 也有 return,但社群通常建議:成功路徑用表達式,發生非預期錯誤才用 return。
基本輸入輸出
Rust 的錯誤處理非常嚴格。看看這個輸入例子:
use std::io::stdin;
fn main() {
let mut s = String::new();
stdin()
.read_line(&mut s)
.expect("fail"); // 失敗了要怎麼辦?Rust 逼你想清楚。
let n: i32 = s.trim().parse().expect("不是數字啊大哥");
println!("你輸入的是:{}", n);
}
你會發現 expect 到處都是。這是因為 Rust 強迫你思考「萬一失敗了怎麼辦」,而不是等到程式執行到一半崩潰才來修。這就是 Rust 能讓程式更健壯的秘密。