iT邦幫忙

0

二、三天學一點點 Rust:來! Structs 與多傳回傳值、實例比較(31)

  • 分享至 

  • xImage
  •  

🦀 Rust 所有權轉移與多重回傳值詳解

本文將詳細解析提供的 Rust 程式碼,重點說明 mut self 參數的使用、所有權轉移機制,以及 Rust 中多重回傳值的實現方式。

🔍 程式碼結構分析

結構體定義

#[derive(Debug)]
struct TaylorSwiftSong {
    title: String,
    year: u32,
    duration_secs: u32,
}

這個結構體定義了一個音樂曲目的資料結構,包含三個欄位:

  • title: String:歌曲標題,使用 String 類型(擁有所有權的字串)
  • year: u32:發行年份,32位元無符號整數
  • duration_secs: u32:持續時間(秒),32位元無符號整數

#[derive(Debug)] 屬性讓編譯器自動為這個結構體實作 Debug trait,使我們能夠使用 {:?} 格式化符號來列印整個結構體的內容。

🔍 extra 方法詳細解析

fn extra(mut self) -> (Self, String) {
    self.title.push_str(" Remix!");
    let rewriten_song = format!("{}", self.title);
    (self, rewriten_song)
}

✅ 參數 mut self 的含義

mut self 參數有兩個重要特性:

  1. 所有權轉移(Move)self(不含 &)表示這個方法會取得結構體實例的完整所有權,而不是借用。這意味著:

    • 呼叫此方法後,原始的變數將不能再被使用
    • 結構體的所有權從呼叫者轉移到方法內部
    • 方法可以完全控制這個實例,包括移動、修改或銷毀它
  2. 內部可變性(Mutability)mut 關鍵字讓方法內部可以修改結構體的欄位值,即使原始變數可能不是 mut 宣告的。

✅ 方法內部操作分析

self.title.push_str(" Remix!");

這行程式碼將字串 " Remix!" 附加到原始標題後面。由於 self 是可變的,我們可以直接修改 title 欄位。

let rewriten_song = format!("{}", self.title);

使用 format! 巨集創建一個新的字串,內容是修改後的標題。這裡創建了標題的一個副本。

(self, rewriten_song)

回傳一個包含兩個元素的元組(tuple):修改後的結構體實例和新創建的字串。

🔍 所有權轉移詳細分析

轉移過程

  1. 呼叫前song 變數擁有 TaylorSwiftSong 實例的所有權
  2. 呼叫時song.extra() 被呼叫,所有權從 song 轉移到方法的 self 參數
  3. 方法內self 擁有實例的完整所有權,可以自由修改
  4. 回傳時:修改後的實例通過元組回傳,所有權再次轉移給接收變數

🔁 main 函數中的所有權流動

let song = TaylorSwiftSong { ... };  // song 擁有實例
let (song, rewriten_song) = song.extra();  // 所有權轉移並重新接收

這裡使用了解構賦值(destructuring assignment)

  • 原始的 song 變數在呼叫 extra() 後就不能再使用
  • 元組的第一個元素(修改後的結構體)被賦值給新的 song 變數
  • 元組的第二個元素(字串)被賦值給 rewriten_song 變數

雖然兩個 song 變數名稱相同,但實際上是不同的變數,這稱為變數遮蔽(variable shadowing)

📦 Rust 多重回傳值機制

元組回傳的原理

Rust 本身不直接支援多重回傳值,但透過**元組(tuple)**可以達到相同效果:

fn extra(mut self) -> (Self, String)

這個函數簽名表示回傳一個包含兩個元素的元組:

  • 第一個元素類型是 Self(即 TaylorSwiftSong
  • 第二個元素類型是 String

程式執行結果分析

當程式執行時:

  1. 創建標題為 "Blank Space" 的歌曲實例
  2. 呼叫 extra() 方法,標題被修改為 "Blank Space Remix!"
  3. 回傳修改後的實例和標題字串
  4. 列印結果:
    TaylorSwiftSong { title: "Blank Space Remix!", year: 2014, duration_secs: 231 }
    Blank Space Remix!
    

🎵 在方法中比較結構體實例 比較兩首歌曲的長度

在 Rust 中,我們不僅可以為結構體定義操作自身數據的方法,還可以定義需要與另一個同類型實例進行互動的方法,例如比較。這篇教學將透過比較兩首 Taylor Swift 的歌曲長度的範例,來詳細解釋這個概念。

🧩 快速瀏覽

首先,讓我們快速看一下 TaylorSwiftSong 結構體和它的實作區塊。

#[derive(Debug)] // 屬性,讓我們的結構體可以用 `{:?}` 印出,方便調試
struct TaylorSwiftSong {
    title: String,
    year: u32,
    duration_secs: u32,
}

impl TaylorSwiftSong {
    // 這個方法比較 "自己" 和 "另一個" TaylorSwiftSong 實例的長度
    fn is_longer_than(&self, other: &Self) -> bool {
        self.duration_secs > other.duration_secs
    }
}

🔍 比較方法的設計解析

這次的重點是 is_longer_than 這個方法。它的設計非常巧妙且符合 Rust 的所有權原則。

fn is_longer_than(&self, other: &Self) -> bool {
    self.duration_secs > other.duration_secs
}
  1. 參數設計 (&self, other: &Self)
    這個方法的簽名包含了兩個參數,它們都是不可變引用 (immutable references):
    • &self*:
      • 這代表呼叫此方法的那個實例的一個不可變借用。它總是方法的第一個參數,指向「自己」。因為我們只需要讀取歌曲時長來進行比較,而不需要修改或消耗這個實例,所以使用 &self 是最理想的選擇。
    • other: &Self*:
      • 這是方法的第二個參數,用來接收要與之比較的另一個實例,other 是我們為這個參數取的名字,你可以取任何你喜歡的名字(例如 another_song)。
      • &Self 是這個參數的型別。Self (大寫的 S) 是 impl 區塊內的一個特殊別名,它代表我們正在實作的結構體型別,也就是 TaylorSwiftSong。所以 &Self 等同於 &TaylorSwiftSong
        我們使用引用 &Self 而不是 Self,因為我們同樣只需要讀取另一首歌的時長,並不想取得它的所有權。
  2. 方法主體
    • self.duration_secs > other.duration_secs:這行程式碼非常直觀。它存取 self 實例的 duration_secs 欄位,並將其與 other 實例的 duration_secs 欄位進行比較。比較結果(一個布林值 true 或 false)會被自動回傳,因為它是這個函式的最後一個表達式。

🧩 方法的呼叫與參數對應

現在,讓我們看看 main 函式是如何呼叫這個方法,以及參數是如何對應的。

fn main() {
    let blank_space = TaylorSwiftSong {
        title: String::from("Blank Space"),
        year: 2014,
        duration_secs: 231,
    };

    let all_too_well = TaylorSwiftSong {
        title: String::from("All too well"),
        year: 2012,
        duration_secs: 327,
    };

    // 呼叫方法並進行比較
    if blank_space.is_longer_than(&all_too_well) {
        // ... (印出結果)
    } else {
        // ... (印出結果)
    }
}

blank_space.is_longer_than(&all_too_well) 這行程式碼如何運作?

  • blank_space.: 當你使用點號 (.) 來呼叫一個方法時,點號左邊的實例 (blank_space) 會自動被傳遞為方法的 self 參數。所以在 is_longer_than 方法內部,self 就代表 blank_space 這個實例。

  • is_longer_than(...): 這是我們要呼叫的方法。

  • &all_too_well: 這是我們傳遞給方法的第一個實際參數(self 是隱式傳遞的)。這個值會對應到方法簽名中的第二個參數,也就是 other: &Self

    • 為何是 &all_too_well 而不是 all_too_well
      • 我們來看看方法簽名的要求:other: &Self。它明確指出 other 參數的型別必須是 &TaylorSwiftSong,也就是一個引用。因此,為了滿足這個型別要求,我們必須傳遞 all_too_well 的一個引用,寫法就是 &all_too_well
      • 如果你只寫 all_too_well,你會嘗試移動 all_too_well 的所有權,但函式期望的是一個引用,這會導致型別不匹配的編譯錯誤。

✅ 總結對應關係:

blank_space.is_longer_than(&all_too_well) 這次呼叫中:

  • self 👉 blank_space
  • other 👉 all_too_well
    因此,方法內部的比較 self.duration_secs > other.duration_secs 實際上就是 blank_space.duration_secs > all_too_well.duration_secs,即 231 > 327,結果為 false。程式最終會執行 else 區塊的程式碼。

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

尚未有邦友留言

立即登入留言