Ch2:變數、結構、列舉 — 各種資料的形狀
·
Sean Sie
宣告:預設就是「乖乖牌」
在 Rust,變數預設是不可變的(Immutable)。這不是編譯器在找你麻煩,而是為了追求極致的效能與安全性。不可變變數讓編譯器能大膽地進行優化(例如直接內聯數值),同時在多執行緒環境下完全杜絕「資料競爭」。
let mut variable: i32 = 1;
variable = 2; // 沒問題,因為有 mut
⚡ 極速講解
- let:宣告關鍵字。在記憶體中劃出一塊領地。
- mut:代表這變數是個「過動兒」,可以修改。Rust 故意讓這關鍵字多三個字母,就是要讓你每打一次 mut 就反省一次:真的需要修改嗎?
- :i32:型態標示。Rust 會盡力幫你猜(型態推論),但如果它猜不到,它會噴紅字求救。
- 預設安全:如果你嘗試修改一個沒加 mut 的變數,編譯器會噴出史上最親切的錯誤訊息,甚至告訴你該在哪裡補上 mut。
遮擋 (Shadowing):不想取名字的救星
遮擋跟 mut 完全不同。mut 是在同一個空間改數值,遮擋則是開一塊新空間,把舊的名字搶過來用,然後把舊的變數丟進碎紙機。
let space = " "; // 這是一個字串切片
let space = space.len(); // 現在 space 變成了一個數字 (usize)
為什麼要用它?
- 型態轉換:不需取名 space_str 和 space_int,讓你的命名空間保持乾淨。
- 不可變性保護:轉換完後,新的 space 依然是不可變的,避免了後續不小心誤改數值的風險。
未使用變數:給編譯器的「封口費」
Rust 編譯器非常囉唆,如果你宣告了一個變數卻從未讀取它,它會一直噴警告訊息給你。如果你確定這個變數現在不用(或是為了以後留著),請在名稱前面加上底線 _。
let _unused_variable = 100; // 這樣編譯器就不會碎碎念了
let _ = 5 * 5; // 如果你連名字都懶得取,直接用底線接住它
數字與純量型態 (Scalars)
1. 整數家族:挑選合身的衣服
Rust 的整數分得很細,因為它在意每一 bit 的記憶體。
| 型態 | 有號 (Signed) | 無號 (Unsigned) | 說明 |
|---|---|---|---|
| 8-bit | i8 | u8 | 小數字,u8 常代表位元組 (Byte) |
| 32-bit | i32 | u32 | 預設首選,大部分 CPU 跑最快 |
| Arch | isize | usize | 根據 CPU 位元數決定,陣列索引必用 |
- ⚡ 溢位行為:開發模式下溢位會直接 panic 報警;發布模式下會「環繞」 (Wrap-around),即 255 + 1 = 0。如果你想要確定的溢位行為,請使用
wrapping_add或checked_add。
2. 布林與字元
- bool:只有 true 和 false。
- char:支援 Unicode,一個佔 4 bytes。這代表你可以用表情符號當字元:
let robot = '🤖';。
線性容器與記憶體窗戶
1. Array (陣列):[1, 2, 3]
- 長度固定:在編譯時就要確定大小,這讓它能穩穩地待在 Stack。
- 語法糖:
let a = [3; 5];等同於[3, 3, 3, 3, 3]。 - 安全性:如果你敢嘗試存取
a[10],Rust 會在編譯期攔截你;如果索引是變數,則會在執行期 panic。永遠沒有緩衝區溢位。
2. Vec (動態陣列):vec![1, 2, 3]
- 長度可變:存在 Heap,受所有權管理。
3. 切片 (Slice):觀察資料的窗戶
切片 &[T] 是 Rust 最精華的設計之一。它不擁有資料,它只是指向資料的一段區間。
let arr = [1, 2, 3, 4, 5];
let slice = &arr[1..4]; // 指向 [2, 3, 4]
- ⚡ 胖指標 (Fat Pointer):切片在底層由兩個資訊組成:指標 + 長度。這讓它比 C 語言的指標更安全,因為它知道自己幾兩重。
- 字串切片 &str:最常用的切片,通常指向 String 的一部分或是靜態文字。
鍵值對容器:HashMap 與 BTreeMap
當你需要「用 A 查 B」時,這兩位就派上用場了。注意:它們不在預設匯入中,必須先 use std::collections::*;。
- HashMap<K, V> (無序雜湊表):
- 特性:基於雜湊演算法。平均存取時間是 O(1)。
- 代價:資料是亂序的,記憶體佔用較多。
- BTreeMap<K, V> (有序 B 樹):
- 特性:資料會依照 Key 自動排序。存取時間是 O(log n)。
- 優勢:適合「範圍查詢」。
⚡ 終極黑科技:Entry API
解決「檢查是否存在,不存在則插入」的囉唆邏輯:
let mut counts = HashMap::new();
for word in "hello world hello".split_whitespace() {
// 如果單字不在 Map 裡,插入 0,然後把它 +1
counts.entry(word).and_modify(|c| *c += 1).or_insert(1);
}
結構 (Struct) 與 元組 (Tuple)
Tuple (元組):(255, 128, 0)
適合打包臨時的資料。透過 tuple.0 存取。括號 () 叫 Unit Type,代表空。
Struct (結構):資料的 AND
將相關聯的欄位命名組合在一起。
struct User {
name: String,
active: bool,
}
進階玩法:
- Tuple Struct:
struct Color(i32, i32, i32);當你想要名字但不想定義欄位名時用。 - 更新省略記號:
..user1可以快速複製其他欄位(注意:這會引發 String 的所有權轉移!)。
列舉 (Enum):Rust 的最強殺手
在 Rust,Enum 是資料的 OR。每個變體(Variant)都可以攜帶不同的資料型態。
enum Action {
Quit, // 無資料
Move { x: i32, y: i32 }, // 匿名結構
Write(String), // 帶有資料
}
Option 與 Result:再見了,Null!
Rust 沒有 null,這逼你必須處理「沒有東西」的情況,根絕 NullPointerException。
- Option
: Some(值)或None。 - Result<T, E>:
Ok(值)或Err(錯誤)。
💡 空指標優化 (Null Pointer Optimization):
Rust 很聰明,Option<&T> 在記憶體中的大小跟 &T 一模一樣!它利用指標不為 0 的特性,將 0 (null) 當作 None。安全且零成本。
API 命名慣例 (不成文規定)
- new():事實上的建構子。
- from() / into():轉換雙子星。實作了 From 就能自動獲得 Into。
- as_xxx:低成本的參考轉換(Cheap)。
- to_xxx:高成本的複製或分配(Expensive)。
掌握這些命名邏輯,你看任何 Crate 的文檔都能事半功倍。