iT邦幫忙

0

二、三天學一點點 Rust:來! Structs 與self、mut self、&self、&mut self(30)

  • 分享至 

  • xImage
  •  

🦀 在結構體上定義方法

在 Rust 中,結構體 (Struct) 讓我們可以將相關數據組織在一起。但光有數據還不夠,我們通常還希望能夠對這些數據執行操作。這時,「方法」(Methods) 就登場了。方法是隸屬於特定實例的函式,它們可以存取或修改該實例的數據。

📦impl 關鍵字:為結構體實作方法

要為一個結構體定義方法,我們需要使用 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();
}

✅ 程式碼說明

  1. struct TaylorSwiftSong { ... }

    • 我們定義了一個名為 TaylorSwiftSong 的結構體,它有三個欄位:title (歌曲名稱,String 型別),year (發行年份,u32 型別),以及 duration_secs (歌曲時長,秒,u32 型別)。
    • #[derive(Debug)] 是一個屬性,它讓編譯器自動為我們的結構體產生 Debug Trait 的實作,這樣我們就可以使用 {:?}{:#?} 來印出結構體的內容(雖然在這個範例中沒有直接這樣印出整個 song,但這是個好習慣)。
  2. impl TaylorSwiftSong { ... }

    • 這個區塊開始為 TaylorSwiftSong 結構體定義方法。
  3. fn display_song_info(self) { ... }

    • 這是我們定義的第一個方法,名為 display_song_info
    • 方法是隸屬於實例的函式,第一個參數通常是 self,它代表呼叫該方法的結構體實例本身。
    • 在這個方法中,我們使用 println! 來印出歌曲的標題、年份和時長。我們透過 self.titleself.yearself.duration_secs 來存取實例的欄位。
  4. fn main() { ... }

    • main 函式中,我們創建了一個 TaylorSwiftSong 的實例,名為 song,並初始化其欄位。
    • song.display_song_info();:這是呼叫方法的方式。我們使用點號 (.) 將實例 song 與其方法 display_song_info 連接起來。 (如筆記所述:"value.method(); value.method(3, "apples")")

📦 self 參數的四種形式

在 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();
}

✅ 程式碼說明

  1. self (或 self: Selfself: TaylorSwiftSong) - 取得所有權

    • 形式:直接寫 self,或者明確寫出型別 self: Self (其中 Selfimpl 區塊所針對的型別,即 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);),就會遇到編譯錯誤。
  2. mut self (或 self: mut Selfself: mut TaylorSwiftSong) - 取得所有權並使其在方法內部可變

    • 形式:mut self,或者明確寫出型別 self: mut Selfself: mut TaylorSwiftSong
    • 行為:與 self 形式一樣,當方法的第一個參數是 mut self 時,呼叫該方法會將實例的所有權移動 (move) 到方法中。這意味著呼叫者在方法呼叫後(除非方法將其回傳)就失去了對原始實例的所有權。mut 關鍵字在這裡表示,在方法的作用域內部,這個名為 self 的綁定是可變的。因此,方法可以修改這個它現在擁有的實例的欄位。
    • 優點:允許方法在消耗實例之前對其進行內部狀態的調整。適用於那些需要取得所有權,並在最終處理(例如轉換成另一種型別、執行某些消耗性操作)前修改實例的場景。
    • 缺點:呼叫者會失去所有權,這與 self 形式相同。
    • 使用情境:建構者模式 (Builder Pattern) 的收尾方法:例如,一個 builder 的 build(mut self) 方法,在最後構建目標物件前,可能需要對 builder 自身的內部狀態做一些最終調整。狀態轉換,當一個物件需要轉換到一個新狀態,並且舊狀態不再有效或不再需要時,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 變數已失效,不能再使用
  1. &self (或 self: &Selfself: &TaylorSwiftSong) - 不可變借用
    • 行為:方法以不可變引用 (immutable borrow) 的方式借用實例。方法可以讀取實例的欄位,但不能修改它們。所有權仍然在呼叫者手中。
    • 優點:呼叫者在方法呼叫後仍然可以使用原始實例。可以同時有多個不可變借用。
    • 缺點:方法不能修改實例的狀態。
    • 常見用途:用於只需要讀取數據的方法,例如計算、格式化輸出等。如果 display_song_info 的目的是僅顯示資訊而不消耗歌曲實例,那麼 &self 會是更常見的選擇。
   impl TaylorSwiftSong {
        fn display_song_info(&self) { // 注意這裡的 &self
           println!("Title: {}", self.title);
              ...
       }
   }
  1. &mut self (或 self: &mut Selfself: &mut TaylorSwiftSong) - 可變借用
    • 行為:方法以可變引用 (mutable borrow) 的方式借用實例。方法可以讀取並修改實例的欄位。所有權仍然在呼叫者手中。
    • 優點:允許方法修改實例的狀態,同時呼叫者在方法呼叫後仍能使用(已被修改的)實例。
    • 缺點:Rust 的借用規則規定,在一個特定的作用域內,對於一個可變數據,你只能有一個可變引用,或者任意數量的不可變引用,但不能同時擁有。
    • 常見用途:用於需要改變實例內部狀態的方法,例如 song.add_to_playlist()song.update_duration(new_duration)

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

尚未有邦友留言

立即登入留言