本文 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 複製
..會將未指定的欄位從另一個已存在的實例複製過來。- 只適用於 Copy 或 Clone 的欄位,否則必須自行實作
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 欄位時,會把所有權從原變數移走,導致後續無法使用。 |
以 &str 或 clone()(若必要)避免搬移。 |
Copy vs Clone |
使用 .. 複製時忘記實作 Clone,編譯失敗。 |
為結構體加上 #[derive(Clone)],或自行實作 Clone。 |
| 可見性混亂 | 把所有欄位都設為 pub,破壞封裝。 |
僅公開需要外部直接存取的欄位,其他使用 method 隱藏。 |
過度使用 Default |
盲目依賴 default(),會產生不合邏輯的「零值」實例。 |
只在確定「零值」合理的情況下使用,或自行實作 Default 提供有意義的預設。 |
最佳實踐:
- 提供建構函式:讓使用者不必記住欄位順序,降低錯誤。
- 使用
#[derive(Debug, Clone, Default)]:在開發階段快速印出、複製與預設。 - 盡量保持欄位私有,透過 method 控制變更邏輯。
- 利用
#[non_exhaustive]防止外部硬編碼結構體,提升未來演進的彈性。 - 遵循「所有權 + Borrow」原則:在需要暫時參考資料時使用
&或&mut,避免不必要的所有權搬移。
實際應用場景
- 遊戲開發:玩家、怪物、道具等都可用結構體表示,透過實例化與方法管理狀態變化。
- 網路服務:
HttpRequest、ApiResponse等資料結構,配合serde序列化/反序列化,讓 JSON 與 Rust 之間的轉換自然。 - 設定檔管理:使用
Config結構體搭配Default與toml、yaml解析庫,提供程式啟動時的參數注入。 - 資源池(Connection Pool):
Connection結構體封裝資料庫連線資訊,透過new建構函式產生,並在池中重複使用。 - 硬體抽象層:嵌入式開發中,
Peripheral結構體描述 GPIO、UART 等外設,實例化後可直接呼叫方法操作硬體。
總結
結構體是 Rust 中最基礎、也是最強大的資料抽象工具。透過 正確的實例化方式(字面值、.. 複製、建構函式、Default),我們可以:
- 提升程式可讀性:欄位名稱即是說明。
- 確保記憶體安全:編譯期檢查所有權與借用。
- 方便未來擴充:使用
pub、#[non_exhaustive]、Default等特性,讓 API 在演進時保持相容。
掌握上述概念與最佳實踐,無論是寫小型腳本或大型系統,都能以 安全、表意清晰 的方式管理資料。接下來,你可以嘗試在自己的專案中加入自訂的建構函式與 Default 實作,感受結構體帶來的開發效率與程式品質提升。祝你在 Rust 的旅程中玩得開心、寫得順手!