結構體 (Struct) 是一個用於存放相關數據的容器。是一個自訂的資料型別,它允許我們將多個相關的數據片段組合在一起,並為這個組合賦予一個有意義的名稱。想像一下,如果你想描述一杯咖啡,你需要多個資訊:它的名字、價格、是否為熱飲等等。將這些零散的數據打包成一個名為 Coffee 的單一整體,會讓程式碼更有組織性、更易於理解。
struct Coffee {
    price: i32,
    name: String,
    is_hot: bool,
}
struct 關鍵字:我們使用 struct 關鍵字來開始定義一個結構體。Coffee。按照慣例,結構體的名稱使用「大駝峰式命名法」(PascalCase/CamelCase)。{}:結構體的欄位被包含在一對大括號中。Coffee 這個結構體後,它就成為了我們程式中的一個新的、自訂的資料型別。| 類型 | 範例格式 | 說明 | 
|---|---|---|
| Named Field Struct | struct Coffee { name: String } | 
最常用,有欄位名稱 | 
| Tuple Struct | struct Color(i32, i32, i32); | 
沒有欄位名,像 tuple 的結構體 | 
| Unit Struct | struct Marker; | 
沒有資料,用於型別標記或 trait 實作 | 
一旦我們定義了 Coffee 結構體,就可以像下面這樣創建它的實例:
let mocha = Coffee {
    price: 150,
    name: String::from("Mocha"),
    is_hot: true,
};
定義一個 struct 就像是設計一張藍圖。為了實際使用這張藍圖,我們需要根據它創建一個具體的物件。這個根據型別創建出來的具體值,就稱為「實例 (Instance)」
{} 並且為所有欄位提供初始值。struct 實例是不可變的,除非用 mut 宣告println!(
    "My {} this morning cost ${}, It's {} that it was hot.",
    mocha.name, mocha.price, mocha.is_hot
);
要讀取或存取結構體實例中欄位的值,我們使用「點號標記法」(dot notation),格式為 實例變數.欄位名稱
String "Mocha"。i32 值 150。bool 值 true。println! 巨集只是「借用」(borrow) 了這些值來顯示,所以 mocha 實例的所有權沒有發生任何變化,它仍然完整地擁有其所有數據。現在,讓我們來看範例中引發所有權問題的部分:
let new_coffee = mocha.name;
println!("{}", new_coffee);
println!("{}", mocha.name); // 這裡會報錯 ❌
let new_coffee = mocha.name;
mocha.name 欄位的型別是 String。String 型別的數據儲存在堆積 (Heap) 上,並且沒有實作 Copy Trait。Copy Trait 的型別,賦值操作會導致所有權的「移動」(Move)。mocha.name 的所有權從 mocha 實例移動到了新的變數 new_coffee 身上。此時,new_coffee 成為了這個 String 的新擁有者。println!("{}", mocha.name); — 為何會出錯?
因為 mocha.name 的所有權已經在上一行被移走了,所以 mocha 實例不再擁有 name 欄位的數據。
當你嘗試再次存取 mocha.name 時,Rust 的編譯器會阻止你,因為你正在嘗試使用一個已經被移動走的值。這就是 Rust 如何在編譯時期就保證記憶體安全的。編譯器會提示類似「borrow of moved value: mocha.name」的錯誤。
部分移動 (Partial Move)
值得注意的是,只有 name 欄位的所有權被移走了。mocha 的其他欄位,如 price (型別 i32) 和 is_hot (型別 bool),因為它們的型別都實作了 Copy Trait,所以在存取時會進行「複製」(Copy) 而不是「移動」。因此,在 mocha.name 被移走後,你仍然可以存取 mocha.price 和 mocha.is_hot。這種情況被稱為「部分移動」。
如果你只是想讀取 mocha.name 的值而不轉移其所有權,你可以「借用」它:
let new_coffee_ref = &mocha.name; // & 創建了一個引用 (借用),所有權沒有移動
println!("{}", new_coffee_ref);
println!("{}", mocha.name); // 這行現在可以正常運作了!
建立結構體實例後,有時你會需要修改其中的欄位值。這在 Rust 中是允許的,但有一個前提:你必須使用 mut 關鍵字將實例標記為可變。請看以下範例:
let mut beverage = Coffee {
    price: 150,
    name: String::from("Mocha"),
    is_hot: true,
};
引用前面的例子,並將let mocha = Coffee{..}的變數改掉,加入mut,此時的 let mut beverage 表示 beverage 這個變數是可變的,我們才能修改它內部的欄位。接下來我們分別對三個欄位做了修改:
beverage.price = 170;
beverage.name = String::from("Caramel Macchiato");
beverage.is_hot = false;
每一行的語法都是:實例變數.欄位 = 新值;。
price 的數值從 150 改為 170。name 被賦予一個新的 String,這是因為 name 是堆積型別(heap type)。is_hot 被設為 false,表示這杯飲料不再是熱的。最後我們使用 println! 印出新的欄位值,這也再次驗證我們已成功修改了原始資料。
| 概念 | 說明 | 
|---|---|
| Struct 定義 | 使用 struct 名稱 { 欄位: 型別, ... } 建立自訂型別 | 
| 實例創建 | 使用 {} 並指定所有欄位值 | 
| 欄位存取 | 使用 實例.欄位 方式 | 
| 擁有權移轉 | 若欄位型別為 String 等非 Copy 型別,讀取會移走所有權 |