Rust struct enum impl match pattern matching

[Rust] 結構體與列舉

所有權的觀念建立後,接著輪到實際承載所有權的單位——自訂型別。Rust 的 struct 跟其他語言差不多,但 enum 完全是另一個層級:每個變體可以帶資料,配上 match 模式匹配,能取代繼承、null、type guard 一票東西。這篇把 struct、enum、Optionmatchif let / while let / let else 一次串完。

結構體(struct)

把多個有命名的欄位包成一個型別。

struct User {
    username: String,
    email: String,
    age: u32,
    active: bool,
}

fn main() {
    let user = User {
        username: String::from("jeremy"),
        email: String::from("jeremy@example.com"),
        age: 30,
        active: true,
    };

    println!("{}", user.username);
}

要修改欄位,整個 instance 必須是 mut(Rust 不允許「只有某幾個欄位 mut」):

let mut user = User { /* ... */ };
user.age = 31;

Field init shorthand

變數名跟欄位名一樣時可以省略:

fn build_user(username: String, email: String) -> User {
    User {
        username,         // 等同 username: username
        email,
        age: 0,
        active: true,
    }
}

Struct update syntax

從另一個 instance 補齊剩下的欄位:

let user2 = User {
    email: String::from("new@example.com"),
    ..user1     // 其餘欄位從 user1 帶過來
};

注意:..user1 對非 Copy 欄位會 move,所以 user1 之後可能就不能用了(取決於哪些欄位被 move)。

Tuple struct

有型別、沒欄位名:

struct Color(u8, u8, u8);
struct Point(f64, f64);

let red = Color(255, 0, 0);
let p = Point(1.0, 2.0);
println!("{} {}", red.0, p.1);

適合「我想要一個獨立型別,但欄位名字本身沒意義」的場景,例如顏色三原色、座標。

Unit struct

沒欄位的型別,常拿來當 marker 或實作 trait 用:

struct AlwaysEqual;
let _x = AlwaysEqual;

impl:給 struct 加方法

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

impl Rectangle {
    // 關聯函式(associated function),沒有 self,類似靜態方法
    fn new(width: u32, height: u32) -> Self {
        Rectangle { width, height }
    }

    fn square(size: u32) -> Self {
        Rectangle { width: size, height: size }
    }

    // 方法(method),第一個參數是 self / &self / &mut self
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn scale(&mut self, factor: u32) {
        self.width *= factor;
        self.height *= factor;
    }

    fn into_square(self) -> Rectangle {
        // 接收 self(不是 &self)= 取走所有權
        Rectangle::square(self.width.max(self.height))
    }
}

fn main() {
    let mut rect = Rectangle::new(10, 20);  // :: 呼叫關聯函式
    println!("{}", rect.area());            // .  呼叫方法
    rect.scale(2);
    let sq = rect.into_square();            // rect 被 move 了
    println!("{}", sq.area());
}

self 三種寫法的差異:

寫法意義用途
self取走所有權變換、消費掉 instance
&self不可變借用唯讀方法(getter、計算)
&mut self可變借用修改欄位

列舉(enum)

定義一個型別,它的值「是這幾種變體之一」:

enum Direction {
    North,
    South,
    East,
    West,
}

let d = Direction::North;

Rust 的 enum 比多數語言強大,因為每個變體可以帶資料

enum Message {
    Quit,                       // 沒資料
    Move { x: i32, y: i32 },    // struct 風格
    Write(String),              // tuple 風格,1 個值
    ChangeColor(u8, u8, u8),    // tuple 風格,3 個值
}

let m1 = Message::Quit;
let m2 = Message::Move { x: 10, y: 20 };
let m3 = Message::Write(String::from("hi"));
let m4 = Message::ChangeColor(255, 0, 0);

同樣的東西在其他語言通常要寫一堆 class 配繼承才能表達。

enum 也能 impl 方法

impl Message {
    fn describe(&self) -> &str {
        match self {
            Message::Quit => "quit",
            Message::Move { .. } => "move",
            Message::Write(_) => "write",
            Message::ChangeColor(..) => "color",
        }
    }
}

內建:Option<T>

Rust 沒有 null。要表達「可能有值,可能沒有」,用標準庫的 Option

enum Option<T> {
    Some(T),
    None,
}

因為太常用,SomeNone 不需要 Option:: 前綴:

let x: Option<i32> = Some(5);
let y: Option<i32> = None;

這個設計逼你明確處理「沒值」的情況,徹底消滅 NullPointerException 這類問題。

match:模式匹配

match 是 Rust 的核心控制流之一,必須窮舉所有可能才會編譯通過。

fn describe(n: Option<i32>) -> String {
    match n {
        Some(0) => String::from("zero"),
        Some(x) if x < 0 => format!("negative {x}"),  // match guard
        Some(x) => format!("positive {x}"),
        None => String::from("nothing"),
    }
}

漏掉一個 arm 編譯就過不了:

match some_option {
    Some(x) => println!("{x}"),
    // ❌ error: non-exhaustive patterns: `None` not covered
}

解構

struct Point { x: i32, y: i32 }

let p = Point { x: 1, y: 2 };

match p {
    Point { x: 0, y: 0 } => println!("origin"),
    Point { x, y: 0 } => println!("on x-axis at {x}"),
    Point { x: 0, y } => println!("on y-axis at {y}"),
    Point { x, y } => println!("({x}, {y})"),
}

範圍與多重匹配

let n = 7;
match n {
    1 => println!("one"),
    2 | 3 | 5 | 7 => println!("prime"),  // 多重
    4..=10 => println!("4 to 10"),       // 範圍(包含)
    _ => println!("other"),              // 萬用
}

enum 變體解構

enum Shape {
    Circle(f64),
    Rectangle { width: f64, height: f64 },
}

fn area(s: &Shape) -> f64 {
    match s {
        Shape::Circle(r) => std::f64::consts::PI * r * r,
        Shape::Rectangle { width, height } => width * height,
    }
}

if let:只關心一個 case

只在乎其中一種情況時,寫整個 match 太囉嗦:

let x = Some(5);

// match 寫法
match x {
    Some(v) => println!("{v}"),
    _ => (),
}

// if let 寫法(等價,但精簡)
if let Some(v) = x {
    println!("{v}");
}

可以接 else:

if let Some(v) = x {
    println!("{v}");
} else {
    println!("nothing");
}

while let

只要 pattern 還匹配就繼續跑:

let mut stack = vec![1, 2, 3];

while let Some(top) = stack.pop() {
    println!("{top}");
}
// 印 3, 2, 1,stack 空了 pop 回 None,迴圈結束

let else:早退場

值不符合 pattern 就走 else 分支(else 裡必須中斷流程):

fn parse_age(s: &str) -> u32 {
    let Ok(n) = s.parse::<u32>() else {
        return 0;
    };
    n
}

match 或巢狀 if let 平坦許多。

Option<T> 處理「可能沒值」;下一篇的 Result<T, E> 用一樣的 enum + match 模式處理「可能失敗」,是 Rust 錯誤處理的核心。

Latest Updates

  • 2026.06.11 Content updated