本文將詳細解析提供的 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,使我們能夠使用 {:?}
格式化符號來列印整個結構體的內容。
fn extra(mut self) -> (Self, String) {
self.title.push_str(" Remix!");
let rewriten_song = format!("{}", self.title);
(self, rewriten_song)
}
mut self
的含義mut self
參數有兩個重要特性:
所有權轉移(Move):self
(不含 &
)表示這個方法會取得結構體實例的完整所有權,而不是借用。這意味著:
內部可變性(Mutability):mut
關鍵字讓方法內部可以修改結構體的欄位值,即使原始變數可能不是 mut
宣告的。
self.title.push_str(" Remix!");
這行程式碼將字串 " Remix!" 附加到原始標題後面。由於 self
是可變的,我們可以直接修改 title
欄位。
let rewriten_song = format!("{}", self.title);
使用 format!
巨集創建一個新的字串,內容是修改後的標題。這裡創建了標題的一個副本。
(self, rewriten_song)
回傳一個包含兩個元素的元組(tuple):修改後的結構體實例和新創建的字串。
song
變數擁有 TaylorSwiftSong
實例的所有權song.extra()
被呼叫,所有權從 song
轉移到方法的 self
參數self
擁有實例的完整所有權,可以自由修改let song = TaylorSwiftSong { ... }; // song 擁有實例
let (song, rewriten_song) = song.extra(); // 所有權轉移並重新接收
這裡使用了解構賦值(destructuring assignment):
song
變數在呼叫 extra()
後就不能再使用song
變數rewriten_song
變數雖然兩個 song
變數名稱相同,但實際上是不同的變數,這稱為變數遮蔽(variable shadowing)。
Rust 本身不直接支援多重回傳值,但透過**元組(tuple)**可以達到相同效果:
fn extra(mut self) -> (Self, String)
這個函數簽名表示回傳一個包含兩個元素的元組:
Self
(即 TaylorSwiftSong
)String
當程式執行時:
extra()
方法,標題被修改為 "Blank Space Remix!"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
}
(&self, other: &Self)
&self
*:
other: &Self
*:
&Self
是這個參數的型別。Self
(大寫的 S) 是 impl
區塊內的一個特殊別名,它代表我們正在實作的結構體型別,也就是 TaylorSwiftSong
。所以 &Self
等同於 &TaylorSwiftSong
。&Self
而不是 Self
,因為我們同樣只需要讀取另一首歌的時長,並不想取得它的所有權。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 區塊的程式碼。