本文 AI 產出,尚未審核
Rust 課程 – 結構體與方法
主題:結構體(Structs)定義
簡介
在 Rust 中,結構體(struct)是用來把多個相關資料組合成一個自訂型別的基礎工具。它不僅讓程式碼更具可讀性,也為後續的方法(method)與trait實作提供了載體。對於想要寫出安全、效能佳且易於維護的程式,掌握結構體的定義與使用是必不可少的步驟。
本單元將從最簡單的「tuple struct」談起,逐步深入到具名欄位的「classic struct」與「unit‑like struct」。每個概念都會配合實作範例,說明 為什麼 以及 怎麼 在真實專案中使用它們。
核心概念
1. 為什麼要使用結構體?
- 資料封裝:把相關的欄位聚合在一起,避免散落在全域變數或函式參數中。
- 型別安全:Rust 的編譯器會檢查每個欄位的型別,減少因錯誤傳遞資料而產生的 bug。
- 可擴充性:日後若需要新增欄位,只要在 struct 定義中加入即可,呼叫端只需要更新建構子或使用具名參數。
2. 結構體的三種基本形態
| 形態 | 語法範例 | 使用情境 |
|---|---|---|
| Unit‑like struct | struct Empty; |
僅作為「標記」或「型別」使用,例如實作 trait 的旗標。 |
| Tuple struct | struct Color(u8, u8, u8); |
欄位較少且不需要欄位名稱時,類似 C 的 struct。 |
| Classic struct | struct Person { name: String, age: u8 } |
最常見,欄位名稱清晰、可自行命名。 |
以下分別說明每種形態的寫法與注意點。
2.1 Unit‑like struct
// 定義一個空結構體,常用來作為 trait 的實作標記
struct Marker;
// 為 Marker 實作一個空的 trait
trait Printable {}
impl Printable for Marker {}
fn main() {
// 雖然 Marker 沒有任何欄位,但仍然可以建立實例
let _m = Marker;
println!("Marker 已建立,並可用於 trait bound");
}
重點:即使沒有欄位,Rust 仍會為它產生唯一的型別,適合在 泛型約束 中使用。
2.2 Tuple struct
// 定義一個代表 RGB 顏色的 tuple struct
struct Rgb(u8, u8, u8);
impl Rgb {
// 取得每個顏色分量的 getter
fn red(&self) -> u8 { self.0 }
fn green(&self) -> u8 { self.1 }
fn blue(&self) -> u8 { self.2 }
}
fn main() {
let sky = Rgb(135, 206, 235);
println!("天空的藍色分量是 {}", sky.blue());
}
- 欄位存取:使用
.0,.1,.2直接索引。 - 適用情境:資料結構簡單且不需要欄位名稱的情況,如 座標點、錯誤碼 等。
2.3 Classic struct
// 定義一個描述使用者的結構體
#[derive(Debug)]
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
// 建構子(使用具名參數)
// 注意:欄位的順序不必與 struct 定義相同
fn new_user(name: &str, mail: &str) -> User {
User {
username: name.to_string(),
email: mail.to_string(),
sign_in_count: 1,
active: true,
}
}
fn main() {
let mut alice = new_user("alice", "alice@example.com");
println!("{:?}", alice);
// 使用可變借用修改欄位
alice.email = "alice@newdomain.org".to_string();
println!("更新後的 email: {}", alice.email);
}
- 具名欄位 讓程式碼更具可讀性。
#[derive(Debug)]為結構體自動產生fmt::Debug實作,方便println!("{:?}", ...)調試。- 可變性:若要修改欄位,必須將實例宣告為
mut,且欄位本身不可是&(借用)或&mut(可變借用)除非使用 所有權 轉移。
3. 結構體的所有權與借用
#[derive(Debug)]
struct Book {
title: String,
author: String,
}
// 取得 Book 的所有權
fn take_book(b: Book) {
println!("我拿到了書:{:?}", b);
}
// 只借用 Book,允許呼叫端繼續使用
fn borrow_book(b: &Book) {
println!("我只借用了書:{} by {}", b.title, b.author);
}
fn main() {
let my_book = Book {
title: "Rust 程式設計".to_string(),
author: "王小明".to_string(),
};
borrow_book(&my_book); // 只借用,不會移動所有權
take_book(my_book); // 移動所有權,my_book 後面不可再使用
// println!("{:?}", my_book); // 這行會編譯錯誤
}
- 所有權轉移(move)會使原變數失效,必須小心使用。
- 借用(reference)則允許多個讀取或單一可變讀取,符合 Rust 的安全模型。
4. 結構體的可見性(Visibility)
mod user {
// 只公開 struct 名稱,欄位保持私有
pub struct Profile {
pub username: String,
email: String, // 私有欄位
}
impl Profile {
// 公開建構子,內部自行設定私有欄位
pub fn new(name: &str, mail: &str) -> Self {
Profile {
username: name.to_string(),
email: mail.to_string(),
}
}
// 公開 getter 取得私有欄位
pub fn email(&self) -> &str {
&self.email
}
}
}
fn main() {
let p = user::Profile::new("bob", "bob@example.com");
println!("使用者名稱:{}", p.username);
// println!("Email:{}", p.email); // 直接存取會編譯錯誤
println!("Email:{}", p.email());
}
- 使用
pub關鍵字控制 模組層級的可見性。 - 常見做法是 欄位私有、提供公開的 getter / setter,以維持不變性(immutability)或加入驗證邏輯。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記 mut |
想要修改結構體欄位卻忘記在變數前加 mut,編譯錯誤。 |
宣告 let mut foo = ...,或使用 可變借用 &mut foo。 |
| 所有權意外移動 | 把結構體傳給函式時不小心移動所有權,導致後續使用失效。 | 使用 參考(&)或 clone(foo.clone())來保留所有權。 |
| 欄位名稱衝突 | 在同一模組內定義多個相同欄位名稱的 struct,易混淆。 | 以 命名空間(module)或 前綴 方式區分,例如 user::Profile vs admin::Profile。 |
| 過度暴露欄位 | 把所有欄位都設為 pub,失去封裝與驗證的機會。 |
欄位私有化,只公開必要的 getter / setter。 |
忘記 #[derive(Debug)] |
想要印出 struct 卻忘記導入 Debug,編譯錯誤。 |
為常用的 struct 加上 #[derive(Debug, Clone, PartialEq)] 等常見 trait。 |
最佳實踐:
- 盡量使用具名欄位的 classic struct,提升可讀性。
- 保持欄位私有,透過方法提供受控的存取。
- 為重要的 struct 加上
#[derive(Debug, Clone, PartialEq)],減少手動實作的負擔。 - 利用
new或builder pattern建構複雜的結構體,避免長參數列。 - 在模組邊界明確劃分公開介面,讓外部只能看到必要的型別與方法。
實際應用場景
| 場景 | 可能的 struct 設計 | 為什麼適合使用 struct |
|---|---|---|
| Web 伺服器的請求資料 | struct HttpRequest { method: Method, path: String, headers: HashMap<String, String>, body: Vec<u8> } |
把所有請求資訊聚合,方便傳遞給中介層(middleware)或路由處理器。 |
| 遊戲開發的座標與向量 | struct Vec3(f32, f32, f32);(tuple struct)或 struct Position { x: f32, y: f32, z: f32 } |
需要大量計算且欄位固定,tuple struct 可減少記憶體占用與程式碼冗長。 |
| 資料庫 ORM 的模型 | #[derive(Debug, Clone)] struct User { id: i32, name: String, email: String, created_at: NaiveDateTime } |
每一筆資料對應一個 struct,結合 serde、sqlx 等 crate 可自動序列化/反序列化。 |
| 多執行緒共享設定 | pub struct Config { pub max_threads: usize, pub log_level: LogLevel }(欄位公開,配合 Arc<Config>) |
允許多個執行緒讀取相同設定,使用 Arc 共享所有權。 |
| 標記型別(Phantom Types) | struct Meter<T>(f64, std::marker::PhantomData<T>); |
透過 unit‑like struct 結合 PhantomData,在編譯期提供單位檢查,避免單位混淆。 |
總結
- 結構體是 Rust 中最基礎且最強大的資料聚合工具,從簡單的
unit‑like、tuple到完整的classic,每種形態都有其適用情境。 - 正確掌握 所有權、借用與可見性,能讓你寫出安全且易於維護的程式。
- 避免常見的所有權移動與可變性錯誤,並遵循 封裝、導出必要介面、使用 derive 等最佳實踐,將大幅提升開發效率。
- 在實務上,從 Web 請求、遊戲座標、資料庫模型 到 多執行緒設定,結構體都是不可或缺的核心概念。
掌握了結構體的定義與使用,你就能在 Rust 程式碼中建立清晰、可擴充且符合安全保證的資料模型,為後續的 方法(method)、trait 與 泛型 打下堅實基礎。祝你在 Rust 的旅程中寫出更好、更安全的程式!