iT邦幫忙

2025 iThome 鐵人賽

DAY 11
0
Rust

30天Rust從零到全端系列 第 11

Day 11: 結構體:組織相關資料

  • 分享至 

  • xImage
  •  

前言

Hi 大家,現在我們已經掌握了 Rust 的核心概念:所有權、借用和生命週期。今天我們來看看如何組織和構建更複雜的資料結構 - 結構體(Structs)

但為什麼需要結構體?

假設你想表示一個使用者的資料:

// 使用個別變數(不建議)
let name = String::from("張小明");
let email = String::from("ming@example.com");
let age = 25;
let is_active = true;

// 使用元組(稍好但不夠清楚)
let user = (String::from("張小明"), String::from("ming@example.com"), 25, true);
let email = user.1;  // 不直觀,容易出錯

結構體提供了更好的解決方案:

struct User {
    name: String,
    email: String,
    age: u32,
    is_active: bool,
}

let user = User {
    name: String::from("張小明"),
    email: String::from("ming@example.com"),
    age: 25,
    is_active: true,
};

let email = user.email;  // 清楚明確

定義結構體

基本語法

// 定義一個任務結構體
struct Task {
    id: u32,
    title: String,
    description: String,
    is_completed: bool,
    priority: u32,
}

// 建立實例
let task = Task {
    id: 1,
    title: String::from("學習 Rust 結構體"),
    description: String::from("理解結構體的定義和使用方法"),
    is_completed: false,
    priority: 1,
};

使用欄位

fn main() {
    let mut task = Task {
        id: 1,
        title: String::from("學習 Rust"),
        description: String::from("每日學習"),
        is_completed: false,
        priority: 1,
    };

    // 讀取欄位
    println!("任務標題: {}", task.title);
    println!("優先順序: {}", task.priority);

    // 修改欄位(需要 mut)
    task.is_completed = true;
    task.priority = 2;
    
    println!("任務狀態: {}", if task.is_completed { "已完成" } else { "未完成" });
}

結構體語法

欄位初始化簡寫

當變數名和欄位名相同時,可以簡寫:

fn create_user(name: String, email: String, age: u32) -> User {
    User {
        name,      // 等同於 name: name
        email,     // 等同於 email: email
        age,       // 等同於 age: age
        is_active: true,
    }
}

結構體更新語法

從其他實例建立新實例:

let user1 = User {
    name: String::from("張小明"),
    email: String::from("ming@example.com"),
    age: 25,
    is_active: true,
};

// 建立新使用者,複用大部分資料
let user2 = User {
    name: String::from("李小華"),
    email: String::from("hua@example.com"),
    ..user1  // 其他欄位使用 user1 的值
};

// 注意:user1 的 age 和 is_active 被移動到 user2
// 如果只有 Copy 型別被複用,user1 仍然可用

元組結構體

有欄位但沒有欄位名的結構體:

// 定義元組結構體
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
    
    // 存取欄位使用索引
    println!("顏色: RGB({}, {}, {})", black.0, black.1, black.2);
    
    // 解構賦值
    let Point(x, y, z) = origin;
    println!("座標: ({}, {}, {})", x, y, z);
}

類單元結構體

沒有任何欄位的結構體:

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
    // 主要用於實作特徵,稍後學習
}

結構體方法

定義方法

使用 impl 區塊定義方法:

impl Task {
    // 關聯函式(類似靜態方法)
    fn new(id: u32, title: String, description: String) -> Task {
        Task {
            id,
            title,
            description,
            is_completed: false,
            priority: 1,
        }
    }
    
    // 實例方法
    fn complete(&mut self) {
        self.is_completed = true;
    }
    
    fn is_high_priority(&self) -> bool {
        self.priority >= 3
    }
    
    fn display(&self) {
        let status = if self.is_completed { "✓" } else { "○" };
        println!("{} [{}] {} - 優先順序: {}", 
                 status, self.id, self.title, self.priority);
    }
    
    // 消費 self 的方法
    fn archive(self) -> String {
        format!("任務 '{}' 已歸檔", self.title)
    }
}

使用方法

fn main() {
    // 使用關聯函式建立實例
    let mut task = Task::new(
        1,
        String::from("學習結構體"),
        String::from("掌握 Rust 結構體的用法")
    );
    
    // 調用實例方法
    task.display();
    
    if !task.is_high_priority() {
        println!("設定高優先順序");
        task.priority = 5;
    }
    
    task.complete();
    task.display();
    
    // 歸檔任務(消費所有權)
    let message = task.archive();
    println!("{}", message);
    
    // task 已經被移動,不能再使用
    // task.display(); // 錯誤!
}

實戰:任務管理系統

建立一個完整的任務管理結構體:

use std::fmt;

#[derive(Debug, Clone)]
enum Priority {
    Low = 1,
    Medium = 2,
    High = 3,
    Urgent = 4,
}

impl fmt::Display for Priority {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Priority::Low => write!(f, "低"),
            Priority::Medium => write!(f, "中"),
            Priority::High => write!(f, "高"),
            Priority::Urgent => write!(f, "緊急"),
        }
    }
}

#[derive(Debug)]
struct Task {
    id: u32,
    title: String,
    description: String,
    is_completed: bool,
    priority: Priority,
    tags: Vec<String>,
}

impl Task {
    fn new(id: u32, title: String, description: String) -> Self {
        Task {
            id,
            title,
            description,
            is_completed: false,
            priority: Priority::Medium,
            tags: Vec::new(),
        }
    }
    
    fn set_priority(&mut self, priority: Priority) {
        self.priority = priority;
    }
    
    fn add_tag(&mut self, tag: String) {
        self.tags.push(tag);
    }
    
    fn complete(&mut self) {
        self.is_completed = true;
    }
    
    fn is_high_priority(&self) -> bool {
        matches!(self.priority, Priority::High | Priority::Urgent)
    }
    
    fn get_summary(&self) -> String {
        format!("[{}] {} - {}", 
                if self.is_completed { "✓" } else { "○" },
                self.title, 
                self.priority)
    }
}

impl fmt::Display for Task {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.get_summary())?;
        if !self.tags.is_empty() {
            write!(f, " 標籤: [{}]", self.tags.join(", "))?;
        }
        Ok(())
    }
}

#[derive(Debug)]
struct TaskManager {
    tasks: Vec<Task>,
    next_id: u32,
}

impl TaskManager {
    fn new() -> Self {
        TaskManager {
            tasks: Vec::new(),
            next_id: 1,
        }
    }
    
    fn add_task(&mut self, title: String, description: String) -> u32 {
        let task = Task::new(self.next_id, title, description);
        let id = task.id;
        self.tasks.push(task);
        self.next_id += 1;
        id
    }
    
    fn get_task_mut(&mut self, id: u32) -> Option<&mut Task> {
        self.tasks.iter_mut().find(|task| task.id == id)
    }
    
    fn complete_task(&mut self, id: u32) -> bool {
        if let Some(task) = self.get_task_mut(id) {
            task.complete();
            true
        } else {
            false
        }
    }
    
    fn list_tasks(&self) {
        if self.tasks.is_empty() {
            println!("沒有任務");
            return;
        }
        
        println!("所有任務:");
        for task in &self.tasks {
            println!("  {}", task);
        }
    }
    
    fn list_pending_tasks(&self) {
        let pending: Vec<_> = self.tasks.iter()
            .filter(|task| !task.is_completed)
            .collect();
            
        if pending.is_empty() {
            println!("沒有待辦任務!");
            return;
        }
        
        println!("待辦任務:");
        for task in pending {
            println!("  {}", task);
        }
    }
    
    fn get_task_count(&self) -> (usize, usize) {
        let total = self.tasks.len();
        let completed = self.tasks.iter()
            .filter(|task| task.is_completed)
            .count();
        (completed, total)
    }
}

使用任務管理系統

fn main() {
    let mut manager = TaskManager::new();
    
    // 新增任務
    let id1 = manager.add_task(
        String::from("學習 Rust 結構體"),
        String::from("理解結構體的定義和使用")
    );
    
    let id2 = manager.add_task(
        String::from("實作任務管理器"),
        String::from("建立一個完整的任務管理系統")
    );
    
    let id3 = manager.add_task(
        String::from("寫測試"),
        String::from("為任務管理器寫單元測試")
    );
    
    // 設定優先順序和標籤
    if let Some(task) = manager.get_task_mut(id1) {
        task.set_priority(Priority::High);
        task.add_tag(String::from("學習"));
        task.add_tag(String::from("基礎"));
    }
    
    if let Some(task) = manager.get_task_mut(id2) {
        task.set_priority(Priority::Urgent);
        task.add_tag(String::from("實作"));
        task.add_tag(String::from("專案"));
    }
    
    // 列出所有任務
    manager.list_tasks();
    println!();
    
    // 完成一個任務
    if manager.complete_task(id1) {
        println!("任務 {} 已完成!", id1);
    }
    println!();
    
    // 列出待辦任務
    manager.list_pending_tasks();
    println!();
    
    // 顯示統計
    let (completed, total) = manager.get_task_count();
    println!("任務統計: {}/{} 已完成 ({:.1}%)", 
             completed, total, 
             (completed as f64 / total as f64) * 100.0);
}

結構體與所有權

所有權轉移

fn process_task(task: Task) -> String {
    // task 的所有權被轉移到這個函式
    format!("處理任務: {}", task.title)
    // task 在函式結束時被丟棄
}

fn main() {
    let task = Task::new(1, String::from("測試"), String::from("測試所有權"));
    
    let result = process_task(task);
    println!("{}", result);
    
    // println!("{:?}", task); // 錯誤!task 已被移動
}

借用結構體

fn analyze_task(task: &Task) -> String {
    // 只借用 task,不取得所有權
    format!("任務分析: {} - 狀態: {}", 
            task.title,
            if task.is_completed { "已完成" } else { "進行中" })
}

fn modify_task(task: &mut Task) {
    // 可變借用,可以修改
    if !task.is_completed {
        task.priority = Priority::High;
    }
}

fn main() {
    let mut task = Task::new(1, String::from("測試"), String::from("測試借用"));
    
    let analysis = analyze_task(&task);  // 不可變借用
    println!("{}", analysis);
    
    modify_task(&mut task);  // 可變借用
    
    println!("修改後: {:?}", task);  // task 仍然有效
}

結構體的特徵

使用 #[derive] 自動實作常用特徵:

#[derive(Debug, Clone, PartialEq, Eq)]
struct SimpleTask {
    id: u32,
    title: String,
    completed: bool,
}

fn main() {
    let task1 = SimpleTask {
        id: 1,
        title: String::from("任務1"),
        completed: false,
    };
    
    // Debug: 可以使用 {:?} 格式化
    println!("Debug: {:?}", task1);
    
    // Clone: 可以複製
    let task2 = task1.clone();
    
    // PartialEq: 可以比較相等性
    println!("相等?: {}", task1 == task2);
    
    // 原來的 task1 仍然有效
    println!("原始任務: {:?}", task1);
}

巢狀結構體

結構體可以包含其他結構體:

#[derive(Debug)]
struct Address {
    street: String,
    city: String,
    postal_code: String,
}

#[derive(Debug)]
struct Person {
    name: String,
    email: String,
    address: Address,
}

impl Person {
    fn new(name: String, email: String, address: Address) -> Self {
        Person { name, email, address }
    }
    
    fn full_address(&self) -> String {
        format!("{}, {}, {}", 
                 self.address.street,
                 self.address.city,
                 self.address.postal_code)
    }
}

fn main() {
    let address = Address {
        street: String::from("民生東路一段100號"),
        city: String::from("台北市"),
        postal_code: String::from("10491"),
    };
    
    let person = Person::new(
        String::from("張小明"),
        String::from("ming@example.com"),
        address
    );
    
    println!("姓名: {}", person.name);
    println!("地址: {}", person.full_address());
    println!("完整資訊: {:#?}", person);
}

常見模式

1. 建構函式模式

impl Task {
    // 基本建構函式
    fn new(id: u32, title: String) -> Self {
        Task {
            id,
            title,
            description: String::new(),
            is_completed: false,
            priority: Priority::Medium,
            tags: Vec::new(),
        }
    }
    
    // 建構器模式
    fn with_description(mut self, description: String) -> Self {
        self.description = description;
        self
    }
    
    fn with_priority(mut self, priority: Priority) -> Self {
        self.priority = priority;
        self
    }
    
    fn with_tags(mut self, tags: Vec<String>) -> Self {
        self.tags = tags;
        self
    }
}

// 使用建構器模式
let task = Task::new(1, String::from("學習 Rust"))
    .with_description(String::from("深入學習 Rust 語言"))
    .with_priority(Priority::High)
    .with_tags(vec![
        String::from("學習"),
        String::from("程式設計"),
    ]);

2. 方法鏈接

impl TaskManager {
    fn add_and_configure_task(&mut self, title: String) -> &mut Task {
        let id = self.add_task(title, String::new());
        self.get_task_mut(id).unwrap()
    }
}

// 方法鏈接使用
manager.add_and_configure_task(String::from("新任務"))
    .set_priority(Priority::High);

3. 選項欄位

struct OptionalTask {
    id: u32,
    title: String,
    description: Option<String>,      // 可選的描述
    due_date: Option<String>,         // 可選的截止日期
    assigned_to: Option<String>,      // 可選的負責人
}

impl OptionalTask {
    fn new(id: u32, title: String) -> Self {
        OptionalTask {
            id,
            title,
            description: None,
            due_date: None,
            assigned_to: None,
        }
    }
    
    fn has_due_date(&self) -> bool {
        self.due_date.is_some()
    }
    
    fn set_due_date(&mut self, date: String) {
        self.due_date = Some(date);
    }
}

Follow-up

1. 基礎練習:學生管理系統

建立一個學生結構體,包含以下功能:

struct Student {
    id: u32,
    name: String,
    grades: Vec<f64>,
}

// 實作以下方法:
// - new(id, name) -> Student
// - add_grade(&mut self, grade: f64)
// - average_grade(&self) -> Option<f64>
// - is_passing(&self) -> bool  // 平均分數 >= 60
// - get_summary(&self) -> String

2. 進階練習:圖書館管理

struct Book {
    isbn: String,
    title: String,
    author: String,
    is_available: bool,
}

struct Library {
    books: Vec<Book>,
}

// 實作以下功能:
// - Library::new() -> Library
// - add_book(&mut self, book: Book)
// - find_book(&self, isbn: &str) -> Option<&Book>
// - borrow_book(&mut self, isbn: &str) -> Result<(), String>
// - return_book(&mut self, isbn: &str) -> Result<(), String>
// - available_books(&self) -> Vec<&Book>

3. 挑戰練習:購物車系統

#[derive(Debug, Clone)]
struct Product {
    id: u32,
    name: String,
    price: f64,
}

struct CartItem {
    product: Product,
    quantity: u32,
}

struct ShoppingCart {
    items: Vec<CartItem>,
    discount_rate: f64,  // 折扣率 0.0-1.0
}

// 實作完整的購物車功能:
// - add_item, remove_item, update_quantity
// - calculate_subtotal, calculate_total
// - apply_discount, clear_cart
// - get_item_count, is_empty

常見錯誤

1. 忘記標記 mut

let user = User { /* ... */ };
user.name = String::from("新名字");  // 錯誤!需要 mut

let mut user = User { /* ... */ };
user.name = String::from("新名字");  // 正確

2. 所有權問題

let user1 = User { /* ... */ };
let user2 = User {
    email: String::from("new@example.com"),
    ..user1
};
// user1 的非 Copy 欄位被移動到 user2
println!("{}", user1.name);  // 錯誤!name 已被移動

3. 方法簽名錯誤

impl Task {
    // 錯誤:應該是 &self 而不是 self
    fn display(self) {  
        println!("{}", self.title);
    }  // self 被消費了!
}

明日預告

明天我們將學習 列舉與模式匹配,這是 Rust 另一個強大的特性,讓你能夠表示「多種可能性中的一種」,並且安全地處理所有情況。我們會學習如何定義枚舉型別、使用 match 表達式,以及如何結合結構體建立更表達性的型別系統。


上一篇
Day 10: 生命週期:參考的有效性保證
下一篇
Day 12: 列舉與模式匹配:表達多種可能性
系列文
30天Rust從零到全端15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言