在 Rust 中,結構體 (Struct) 讓我們可以將相關數據組織在一起。但光有數據還不夠,我們通常還希望能夠對這些數據執行操作。這時,「方法」(Methods) 就登場了。方法是隸屬於特定實例的函式,它們可以存取或修改該實例的數據。
要為一個結構體定義方法,我們需要使用 impl 關鍵字,它代表「實作」(implementation)。impl 區塊的格式如下:
impl StructName {
// 方法定義
fn method_name(parameter1: Type1, ...) -> ReturnType {
// 方法的程式碼
}
}
impl StructName
:表示我們接下來要為名為 StructName 的結構體實作方法。impl
區塊內部,我們可以定義一個或多個方法。方法的定義與一般函式非常相似。#[derive(Debug)] // 這是屬性,讓結構體可以用 {:?} 印出,稍後會用到
struct TaylorSwiftSong {
title: String,
year: u32,
duration_secs: u32,
}
// 使用 impl 為 TaylorSwiftSong 結構體實作方法
impl TaylorSwiftSong {
// 定義一個名為 display_song_info 的方法
// 它的第一個參數是 self
fn display_song_info(self) {
println!("Title: {}", self.title);
println!("Release year: {}", self.year);
println!("Duration: {}sec.", self.duration_secs);
}
}
fn main() {
// 創建 TaylorSwiftSong 結構體的一個實例
let song = TaylorSwiftSong {
title: String::from("Black Space"), // 原曲名應為 "Blank Space" 😉
year: 2014,
duration_secs: 231,
};
// 呼叫實例上的方法
// 語法:instance.method_name()
song.display_song_info();
}
struct TaylorSwiftSong { ... }
:
TaylorSwiftSong
的結構體,它有三個欄位:title
(歌曲名稱,String 型別),year
(發行年份,u32 型別),以及 duration_secs
(歌曲時長,秒,u32 型別)。#[derive(Debug)]
是一個屬性,它讓編譯器自動為我們的結構體產生 Debug Trait
的實作,這樣我們就可以使用 {:?}
或 {:#?}
來印出結構體的內容(雖然在這個範例中沒有直接這樣印出整個 song,但這是個好習慣)。impl TaylorSwiftSong { ... }
:
TaylorSwiftSong
結構體定義方法。fn display_song_info(self) { ... }
:
display_song_info
。self
,它代表呼叫該方法的結構體實例本身。println!
來印出歌曲的標題、年份和時長。我們透過 self.title
、self.year
和 self.duration_secs
來存取實例的欄位。fn main() { ... }
:
main
函式中,我們創建了一個 TaylorSwiftSong
的實例,名為 song
,並初始化其欄位。song.display_song_info();
:這是呼叫方法的方式。我們使用點號 (.)
將實例 song
與其方法 display_song_info
連接起來。 (如筆記所述:"value.method(); value.method(3, "apples")")在 Rust 中,方法的第一個參數 self 可以有幾種不同的形式,這會影響到方法如何與實例互動,特別是關於所有權和可變性,根據前述程式碼略做修改,針對參數變化做說明。
#[derive(Debug)]
struct TaylorSwiftSong {
title: String,
year: u32,
duration_secs: u32,
}
impl TaylorSwiftSong {
fn display_song_info(&self) {
println!("Title: {}", self.title);
println!("Release year: {}", self.year);
println!("Duration: {}sec.", self.duration_secs);
}
fn double_secs(&mut self) {
self.duration_secs = self.duration_secs * 2;
println!("Duration: {}sec.", self.duration_secs);
}
}
fn main() {
let mut song = TaylorSwiftSong {
title: String::from("Black Space"),
year: 2014,
duration_secs: 231,
};
song.display_song_info();
song.double_secs();
}
self
(或 self: Self
或 self: TaylorSwiftSong
) - 取得所有權
self
,或者明確寫出型別 self: Self
(其中 Self
是 impl
區塊所針對的型別,即 TaylorSwiftSong
),或者 self: TaylorSwiftSong
。這三種在這種情況下是等效的。self
時,呼叫該方法會將實例的所有權移動 (move) 到方法中。一旦方法執行完畢,如果這個實例沒有被回傳,它就會被銷毀 (drop)。display_song_info(self)
就是這種情況。這意味著當 song.display_song_info();
執行後,song
實例的所有權就被轉移給了 display_song_info
方法。因為 display_song_info
沒有回傳任何東西,所以 song
在方法結束後就被銷毀了。如果我們之後再嘗試使用 song
(例如 println!("{:?}", song);
),就會遇到編譯錯誤。mut self
(或 self: mut Self
或 self: mut TaylorSwiftSong
) - 取得所有權並使其在方法內部可變
mut self
,或者明確寫出型別 self: mut Self
或 self: mut TaylorSwiftSong
。self
形式一樣,當方法的第一個參數是 mut self
時,呼叫該方法會將實例的所有權移動 (move) 到方法中。這意味著呼叫者在方法呼叫後(除非方法將其回傳)就失去了對原始實例的所有權。mut
關鍵字在這裡表示,在方法的作用域內部,這個名為 self
的綁定是可變的。因此,方法可以修改這個它現在擁有的實例的欄位。mut self
允許在轉換(或消耗)前進行修改。// 繼續 impl TaylorSwiftSong { ... }
fn log_and_consume_as_remix(mut self) -> String {
// 因為是 mut self,我們可以修改 self 的欄位
self.title.push_str(" (Remix)"); // 修改了 self 擁有的 title
let log_entry = format!(
"[LOG] Consumed Remix Song: Title: {}, Year: {}, Duration: {}sec.",
self.title, self.year, self.duration_secs
);
// self 的所有權在此結束,它會在這裡被 drop (除非被回傳)
log_entry // 回傳格式化後的字串
}
// } // 結束 impl TaylorSwiftSong
因此這樣呼叫就會:
let song = TaylorSwiftSong { title: String::from("Style"), year: 2014, duration_secs: 230 };
let log_message = song.log_and_consume_as_remix();
println!("{}", log_message);
// // ❌ 此時 song 變數已失效,不能再使用
&self
(或 self: &Self
或 self: &TaylorSwiftSong
) - 不可變借用
display_song_info
的目的是僅顯示資訊而不消耗歌曲實例,那麼 &self
會是更常見的選擇。 impl TaylorSwiftSong {
fn display_song_info(&self) { // 注意這裡的 &self
println!("Title: {}", self.title);
...
}
}
&mut self
(或 self: &mut Self
或 self: &mut TaylorSwiftSong
) - 可變借用
song.add_to_playlist()
或 song.update_duration(new_duration)
。