iT邦幫忙

0

二、三天學一點點 Rust:來! Structs 基本概念(27)

  • 分享至 

  • xImage
  •  

☕ Rust 教學:結構體(Struct)與擁有權概念

結構體 (Struct) 是一個用於存放相關數據的容器。是一個自訂的資料型別,它允許我們將多個相關的數據片段組合在一起,並為這個組合賦予一個有意義的名稱。想像一下,如果你想描述一杯咖啡,你需要多個資訊:它的名字、價格、是否為熱飲等等。將這些零散的數據打包成一個名為 Coffee 的單一整體,會讓程式碼更有組織性、更易於理解。


🏗️ 第一段:定義 Struct 的語法與內容

struct Coffee {
    price: i32,
    name: String,
    is_hot: bool,
}

✅ Struct 語法

  1. struct 關鍵字:我們使用 struct 關鍵字來開始定義一個結構體。
  2. 結構體名稱:接著是結構體的名稱,例如 Coffee。按照慣例,結構體的名稱使用「大駝峰式命名法」(PascalCase/CamelCase)。
  3. 大括號 {}:結構體的欄位被包含在一對大括號中。
  4. 欄位 (Fields):在大括號內,我們定義了結構體的所有欄位。每個欄位都遵循 欄位名稱: 型別 的格式,並以逗號分隔。
    • price: i32:一個名為 price 的欄位,其型別是 i32 (32 位元整數)。
    • name: String:一個名為 name 的欄位,其型別是 String (一個可增長的字串)。
    • is_hot: bool:一個名為 is_hot 的欄位,其型別是 bool (布林值 true 或 false)。
      定義了 Coffee 這個結構體後,它就成為了我們程式中的一個新的、自訂的資料型別。

📦 Rust 有三種結構體

類型 範例格式 說明
Named Field Struct struct Coffee { name: String } 最常用,有欄位名稱
Tuple Struct struct Color(i32, i32, i32); 沒有欄位名,像 tuple 的結構體
Unit Struct struct Marker; 沒有資料,用於型別標記或 trait 實作

🧪 第二段:建立 Struct 實例(Instance)與細節

一旦我們定義了 Coffee 結構體,就可以像下面這樣創建它的實例:

let mocha = Coffee {
    price: 150,
    name: String::from("Mocha"),
    is_hot: true,
};

✅ 實例是什麼?

定義一個 struct 就像是設計一張藍圖。為了實際使用這張藍圖,我們需要根據它創建一個具體的物件。這個根據型別創建出來的具體值,就稱為「實例 (Instance)」

✅ 程式碼說明

  1. 我們使用 let 來宣告一個變數,例如 mocha。
  2. 接著是結構體的名稱 Coffee,後面跟著一對大括號。
  3. 在大括號內,我們為結構體中定義的每一個欄位提供具體的值,格式為 欄位名稱: 值,例如 price: 150。
  4. 需要注意的細節:
    • 在創建實例時,使用 {} 並且為所有欄位提供初始值。
    • 提供的值的型別必須與結構體定義中的型別匹配。例如,price 必須是 i32,name 必須是 String。
    • 欄位初始化的順序不需要和定義時的順序完全一樣,但為了可讀性,建議保持一致。
    • Rust 中 struct 實例是不可變的,除非用 mut 宣告
      現在,變數 mocha 就是 Coffee 型別的一個實例,它包含了這杯摩卡咖啡的所有資訊。

🔍 第三段:讀取欄位並理解擁有權轉移

println!(
    "My {} this morning cost ${}, It's {} that it was hot.",
    mocha.name, mocha.price, mocha.is_hot
);

✅ 如何存取欄位?

要讀取或存取結構體實例中欄位的值,我們使用「點號標記法」(dot notation),格式為 實例變數.欄位名稱

✅ 程式碼說明

  1. mocha.name 會存取到 String "Mocha"
  2. mocha.price 會存取到 i32 值 150。
  3. mocha.is_hot 會存取到 booltrue
  4. 這裡的 println! 巨集只是「借用」(borrow) 了這些值來顯示,所以 mocha 實例的所有權沒有發生任何變化,它仍然完整地擁有其所有數據。

❗ 擁有權問題:

現在,讓我們來看範例中引發所有權問題的部分:

let new_coffee = mocha.name;
println!("{}", new_coffee);
println!("{}", mocha.name); // 這裡會報錯 ❌
  1. let new_coffee = mocha.name;

    • 這一行程式碼看起來只是簡單的賦值,但在 Rust 的所有權系統中,這是一個關鍵操作。
    • mocha.name 欄位的型別是 String。String 型別的數據儲存在堆積 (Heap) 上,並且沒有實作 Copy Trait
    • 對於沒有實作 Copy Trait 的型別,賦值操作會導致所有權的「移動」(Move)。
    • 所以,mocha.name 的所有權從 mocha 實例移動到了新的變數 new_coffee 身上。此時,new_coffee 成為了這個 String 的新擁有者。
  2. println!("{}", mocha.name); — 為何會出錯?
    因為 mocha.name 的所有權已經在上一行被移走了,所以 mocha 實例不再擁有 name 欄位的數據。
    當你嘗試再次存取 mocha.name 時,Rust 的編譯器會阻止你,因為你正在嘗試使用一個已經被移動走的值。這就是 Rust 如何在編譯時期就保證記憶體安全的。編譯器會提示類似「borrow of moved value: mocha.name」的錯誤。

  3. 部分移動 (Partial Move)
    值得注意的是,只有 name 欄位的所有權被移走了。mocha 的其他欄位,如 price (型別 i32) 和 is_hot (型別 bool),因為它們的型別都實作了 Copy Trait,所以在存取時會進行「複製」(Copy) 而不是「移動」。因此,在 mocha.name 被移走後,你仍然可以存取 mocha.pricemocha.is_hot。這種情況被稱為「部分移動」。

✅ 如何解決?

如果你只是想讀取 mocha.name 的值而不轉移其所有權,你可以「借用」它:

let new_coffee_ref = &mocha.name; // & 創建了一個引用 (借用),所有權沒有移動
println!("{}", new_coffee_ref);
println!("{}", mocha.name); // 這行現在可以正常運作了!

🛠️ 修改 Struct 實例的欄位值

建立結構體實例後,有時你會需要修改其中的欄位值。這在 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 型別,讀取會移走所有權

圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言