iT邦幫忙

0

二、三天學一點點 Rust:來! Structs 與函式(29)

  • 分享至 

  • xImage
  •  

☕ 結構體與函式的互動、Display and Debug

在 Rust 中,函式參數的傳遞方式與擁有權、借用(borrowing)息息相關,尤其當傳遞的是 struct 這樣的複合型別時。此外,Rust 也為輸出資料提供了強大的格式化工具,其中最常見的便是 DisplayDebug。本篇將探討函式與結構體互動的幾種方式,以及如何讓我們自訂的結構體能夠方便地顯示出來。


🧭 函式如何接收結構體

函式在接收結構體實例時,可以根據需求採用不同的方式,主要影響到所有權以及是否能修改結構體內容。這四種主要方式是:

  • 傳值 (Take Ownership - fn func(c: Coffee)):函式獲得結構體實例的完整所有權。
  • 不可變借用 (Immutable Borrow - fn func(c: &Coffee)):函式借用結構體實例,但只能讀取,不能修改。
  • 可變借用 (Mutable Borrow - fn func(c: &mut Coffee)):函式借用結構體實例,並且可以修改其內容。
  • 傳值並使其可變 (Take Ownership and make it mutable - fn func(mut c: Coffee)):函式獲得所有權,並且可以在函式內部修改這個實例的副本或移動過來的值。

其中 drink_something 函式使用了「可變借用」:

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

fn main() {
    // 1. 創建一個可變的 Coffee 實例 mocha
    let mut mocha = make_coffee(String::from("Latte"), 130, true);

    // 2. 將 mocha 的可變引用傳遞給 drink_something 函式
    drink_something(&mut mocha);

    // 3. mocha 的 price 欄位已被 drink_something 函式修改
    println!("After drinking, {} costs {}, still hot? We didn't track that change.", mocha.name, mocha.price);

    // (以下為後續討論 Display 和 Debug 的範例,暫與此主題無關)
    let value = ["Hello,", "World!"];
    // println!("{}", value); // 這行直接用 {} 印陣列會報錯,因為陣列沒有直接實作 Display
    println!("Array with Debug: {:?}", value);
    println!("Array with Pretty Debug: {:#?}", value);
}

fn make_coffee(name: String, price: i32, is_hot: bool) -> Coffee {
    Coffee {
        name,
        price,
        is_hot,
    }
}

// drink_something 函式接收一個對 Coffee 實例的可變引用
fn drink_something(coffee: &mut Coffee) {
    coffee.price = 200; // 透過可變引用修改 coffee 的 price 欄位
    println!("Inside drink_something: {}, price changed to {}", coffee.name, coffee.price);
}

此範例程式碼(可變借用):

  1. let mut mocha = ...:在 main 函式中,我們創建了 mocha。注意這裡使用了 mut 關鍵字,因為我們稍後需要取得它的一個可變引用來修改它。
  2. drink_something(&mut mocha)
    • 我們將 mocha 的一個可變引用 (&mut Coffee) 傳遞給 drink_something 函式。
    • 這意味著 main 函式仍然保有 mocha 的所有權,但暫時將「修改權限」借給了 drink_something 函式。
    • drink_something 的生命週期內,main 函式不能再對 mocha 進行其他可變或不可變的借用(這是 Rust 的借用規則,防止數據競爭)。
  3. fn drink_something(coffee: &mut Coffee)
    • 函式簽名表明它接受一個名為 coffee 的參數,其型別是 &mut Coffee
    • 在函式內部,coffee.price = 200; 這行程式碼透過這個可變引用,成功地修改了原始 mocha 實例的 price 欄位的值。
  4. 結果:當 drink_something 函式執行完畢後,控制權回到 main 函式。mocha 的 price 欄位的值已經變成了 200。

其他接收方式簡述:

  • 傳值 (fn func(c: Coffee)):如果 drink_something 的簽名是 fn drink_something(c: Coffee),那麼 mocha 的所有權會被移動到函式中。除非函式再把 c 回傳,否則 main 中的 mocha 將不再可用。
  • 不可變借用 (fn func(c: &Coffee)):如果簽名是 fn view_coffee(c: &Coffee),則函式只能讀取 c 的欄位,不能修改它們。
  • 傳值並使其可變 (fn func(mut c: Coffee)):如果簽名是 fn consume_and_modify(mut c: Coffee)mocha 所有權被移動,且在函式內部 c 是可變的。任何修改只影響這個函式內部的 c,除非 c 被回傳。

選擇哪種方式取決於函式需要對數據做什麼操作:是需要獲取所有權並可能消耗它,還是僅僅讀取它,或是需要修改原始數據。


🧾 格式化輸出與 Debug/Display trait

當我們嘗試印出一個陣列或自訂的結構體時,可能會遇到問題。Rust 的 println! 巨集需要知道如何將一個型別轉換成字串來顯示。這是透過 Display 和 Debug 這兩個 Trait (特徵) 來實現的。

背景知識 (相關概念在此前有討論,這裡再度提起):

  • Trait (特徵):一個 Trait 是一份契約,它規定了某個型別必須實作某些方法。
  • Display 或 Debug Trait:一個實作了 Display 或 Debug Trait 的型別,代表它承諾自己可以被表示為一個字串。
  • Attribute (屬性):屬性是給編譯器的指令。它是位於某個結構之上(通常是前一行)的元數據,用於自訂編譯器解析程式碼的方式。
let value = ["Hello,", "World!"];
println!("{}", value);       // ❌ 錯誤,無 Display trait
println!("{:?}", value);      // ✅ Debug 輸出
println!("{:#?}", value);     // ✅ Debug,格式化輸出
  1. Rust 中,並非所有型別都能直接透過 {} 印出。若要使用 println!("{}"),該型別必須實作 Display trait。陣列 value 就沒有實作 Display,因此會報錯。

  2. 若改用 {:?}{:#?},則是使用 Debug 格式輸出。這要求型別需實作 Debug trait。Rust 提供 #[derive(Debug)] 屬性來自動為 struct 實作 Debug:

// 1. 使用 derive 屬性自動實作 Debug Trait
#[derive(Debug)]
struct Coffee {
    name: String,
    price: i32,
    is_hot: bool,
}

fn main() {
    let mut mocha = make_coffee(String::from("Latte"), 130, true);
    drink_something(&mut mocha);
    // println!("{}, {}", mocha.name, mocha.price); // 之前的印出方式,分別印出欄位

    // 2. 嘗試印出整個 mocha 實例
    // println!("{}", mocha); // 這行仍然會報錯,因為我們沒有手動實作 Display
    println!("Mocha (Debug): {:?}", mocha);
    println!("Mocha (Pretty Debug): {:#?}", mocha);
}

// make_coffee 和 drink_something 函式定義同上
fn make_coffee(name: String, price: i32, is_hot: bool) -> Coffee {....}
fn drink_something(coffee: &mut Coffee) {....}
  1. #[derive(Debug)]
    • 這是 Rust 中的一個屬性 (Attribute)。
    • derive是一個特殊的屬性,它告訴編譯器根據一些預設的規則自動為我們的結構體(或枚舉)產生某些 Trait 的實作程式碼。
    • #[derive(Debug)]指示編譯器自動為Coffee結構體實作 Debug Trait。這意味著Coffee型別的實例現在可以透過 {:?} 或 {:#?} 格式化符來印出了。
  2. 印出mocha實例:println!("{}", mocha);
    • 取消註解,仍然會導致編譯錯誤。因為#[derive(Debug)]只為我們實作了 Debug Trait,並沒有實作 Display Trait。{} 格式化符要求型別實作 Display。若要讓此行運作,我們需要手動為Coffee實作std::fmt::Display Trait,定義它應該如何被「美觀地」顯示給使用者。

📘 小結

主題 重點
Struct 傳參方式 可傳所有權、不可變參照、可變參照,依需求決定
可變參照 &mut 可在不奪走所有權下,修改 Struct 欄位
Display vs Debug {} 需實作 Display,{:?} 則需實作 Debug
Debug 實作 使用 #[derive(Debug)] 為 struct 自動加上 Debug 支援
  • 可以透過傳值(移動所有權)或傳引用(借用,可分為不可變和可變)來接收結構體。選擇哪種方式取決於函式的功能需求。
  • Debug Trait 對於開發和調試非常有用,使用 #[derive(Debug)] 可以輕鬆地為自訂結構體自動實作它。
  • Display Trait 用於定義更使用者友好的字串表示,通常需要手動實作。

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

尚未有邦友留言

立即登入留言