本文 AI 產出,尚未審核

Rust 課程 – 結構體與方法

主題:結構體實例化


簡介

在 Rust 中,**結構體(struct)**是用來描述「資料」的主要工具,類似於其他語言的 class 或 record。透過結構體,我們可以把多個相關的欄位(field)打包成一個具名的型別,讓程式的可讀性與可維護性大幅提升。

而**實例化(instantiation)**則是將結構體型別轉換成實際的資料值(instance),也就是在程式執行時產生可操作的物件。掌握正確的實例化方式,是撰寫安全、有效率 Rust 程式的第一步。

本篇文章將從基本語法、常見寫法、陷阱與最佳實踐,帶你一步步熟悉結構體的建立與使用,並提供實務範例,幫助初學者快速上手,也讓已有基礎的開發者深化理解。


核心概念

1. 基本結構體宣告與實例化

// 定義一個簡單的 Point 結構體
struct Point {
    x: f64,
    y: f64,
}

// 直接使用字面值建立實例
let origin = Point { x: 0.0, y: 0.0 };
  • struct 關鍵字宣告型別,欄位必須指定名稱與型別。
  • 實例化時使用大括號 {},必須為每個欄位提供值,且順序不必與宣告相同。

2. 使用 .. 簡寫建立相似結構體

let p1 = Point { x: 3.5, y: -2.1 };
let p2 = Point { y: 4.0, ..p1 }; // x 會從 p1 複製
  • .. 會將未指定的欄位從另一個已存在的實例複製過來。
  • 只適用於 CopyClone 的欄位,否則必須自行實作 Clone

3. 具名欄位的可見性(pub)

pub struct Rectangle {
    pub width: u32,
    pub height: u32,
}

// 在其他模組中仍可直接存取
let rect = Rectangle { width: 30, height: 50 };
println!("寬 {} 高 {}", rect.width, rect.height);
  • 若結構體本身或欄位未加 pub,則只能在同一模組內使用。
  • 常見做法是只公開必要的欄位,其他保持私有,配合方法(method)提供受控存取。

4. 結構體建構函式(Associated Function)

impl Rectangle {
    // 以關聯函式的方式提供建構子
    pub fn new(width: u32, height: u32) -> Self {
        Self { width, height }
    }
}

let r = Rectangle::new(10, 20);
  • Self 代表當前結構體型別,讓程式碼更具可讀性。
  • 使用 ::new 的慣例讓使用者不必記住欄位順序,降低錯誤機率。

5. 使用 Default Trait 提供預設值

#[derive(Debug, Default)]
struct Config {
    timeout: u64,
    retries: u8,
}

// 呼叫預設實例
let cfg = Config::default(); // timeout = 0, retries = 0
  • 為結構體實作 Default 後,可使用 ::default() 產生「零值」或自訂的預設設定。
  • 常與建構函式結合:Config { timeout: 30, ..Default::default() }

程式碼範例(實用示例)

範例 1:建立一個玩家資料結構

#[derive(Debug)]
struct Player {
    name: String,
    level: u32,
    hp: i32,
}

impl Player {
    fn new(name: &str) -> Self {
        Self {
            name: name.to_string(),
            level: 1,
            hp: 100,
        }
    }

    fn gain_exp(&mut self, exp: u32) {
        self.level += exp / 1000;
    }
}

let mut p = Player::new("Alice");
p.gain_exp(2500);
println!("{:?}", p);
  • 使用 &str 參數避免不必要的所有權搬移。
  • gain_exp可變方法,必須以 &mut self 接收。

範例 2:使用 .. 複製大部分欄位

#[derive(Clone, Debug)]
struct Settings {
    volume: u8,
    brightness: u8,
    language: String,
}

let base = Settings {
    volume: 50,
    brightness: 70,
    language: "zh-TW".to_string(),
};

let night_mode = Settings {
    brightness: 30,
    ..base.clone()
};
println!("{:?}", night_mode);
  • 需要 Clone 才能使用 ..base.clone(),避免所有權被移走。

範例 3:結合 Default 與建構函式

#[derive(Debug, Default)]
struct Server {
    host: String,
    port: u16,
    max_conn: usize,
}

impl Server {
    fn with_host(host: &str) -> Self {
        Self {
            host: host.to_string(),
            ..Default::default()
        }
    }
}

let srv = Server::with_host("127.0.0.1");
println!("{:?}", srv);
  • Default::default() 為未指定欄位提供安全的預設值。

範例 4:公開欄位與私有欄位的混合

pub struct Token {
    pub id: u64,
    secret: String, // 私有欄位
}

impl Token {
    pub fn new(id: u64, secret: &str) -> Self {
        Self {
            id,
            secret: secret.to_string(),
        }
    }

    // 只允許讀取 secret 的雜湊值
    pub fn hash(&self) -> u64 {
        // 簡化的雜湊演算法
        self.secret.bytes().fold(0, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u64))
    }
}
  • 透過 pub 控制欄位可見性,保護敏感資訊。

範例 5:使用 #[non_exhaustive] 防止外部直接建構

#[non_exhaustive]
pub struct ApiResponse {
    pub code: u16,
    pub message: String,
    // 未來可能會加入更多欄位
}

impl ApiResponse {
    pub fn ok(msg: &str) -> Self {
        Self {
            code: 200,
            message: msg.to_string(),
        }
    }
}
  • #[non_exhaustive] 告訴使用者 不能 用字面值直接建構,必須走提供的建構函式,避免未來欄位變動造成相容性問題。

常見陷阱與最佳實踐

陷阱 說明 解決方式
欄位遺漏 實例化時忘記填寫必填欄位,編譯錯誤不易定位。 使用建構函式 (::new) 或 ..Default::default() 讓欄位預設。
所有權搬移 直接使用 String 欄位時,會把所有權從原變數移走,導致後續無法使用。 &strclone()(若必要)避免搬移。
Copy vs Clone 使用 .. 複製時忘記實作 Clone,編譯失敗。 為結構體加上 #[derive(Clone)],或自行實作 Clone
可見性混亂 把所有欄位都設為 pub,破壞封裝。 僅公開需要外部直接存取的欄位,其他使用 method 隱藏。
過度使用 Default 盲目依賴 default(),會產生不合邏輯的「零值」實例。 只在確定「零值」合理的情況下使用,或自行實作 Default 提供有意義的預設。

最佳實踐

  1. 提供建構函式:讓使用者不必記住欄位順序,降低錯誤。
  2. 使用 #[derive(Debug, Clone, Default)]:在開發階段快速印出、複製與預設。
  3. 盡量保持欄位私有,透過 method 控制變更邏輯。
  4. 利用 #[non_exhaustive] 防止外部硬編碼結構體,提升未來演進的彈性。
  5. 遵循「所有權 + Borrow」原則:在需要暫時參考資料時使用 &&mut,避免不必要的所有權搬移。

實際應用場景

  1. 遊戲開發:玩家、怪物、道具等都可用結構體表示,透過實例化與方法管理狀態變化。
  2. 網路服務HttpRequestApiResponse 等資料結構,配合 serde 序列化/反序列化,讓 JSON 與 Rust 之間的轉換自然。
  3. 設定檔管理:使用 Config 結構體搭配 Defaulttomlyaml 解析庫,提供程式啟動時的參數注入。
  4. 資源池(Connection Pool)Connection 結構體封裝資料庫連線資訊,透過 new 建構函式產生,並在池中重複使用。
  5. 硬體抽象層:嵌入式開發中,Peripheral 結構體描述 GPIO、UART 等外設,實例化後可直接呼叫方法操作硬體。

總結

結構體是 Rust 中最基礎、也是最強大的資料抽象工具。透過 正確的實例化方式(字面值、.. 複製、建構函式、Default),我們可以:

  • 提升程式可讀性:欄位名稱即是說明。
  • 確保記憶體安全:編譯期檢查所有權與借用。
  • 方便未來擴充:使用 pub#[non_exhaustive]Default 等特性,讓 API 在演進時保持相容。

掌握上述概念與最佳實踐,無論是寫小型腳本或大型系統,都能以 安全、表意清晰 的方式管理資料。接下來,你可以嘗試在自己的專案中加入自訂的建構函式與 Default 實作,感受結構體帶來的開發效率與程式品質提升。祝你在 Rust 的旅程中玩得開心、寫得順手!