Rust 學習筆記:列舉與模式配對

2023年10月9日 · 中文


前言

本系列文書寫我個人初探 Rust 的學習筆記,章節劃分主要基於著名的 The BookThe Rust Programming Language,程式碼部分通常是個人閱讀消化後以方便說明的方式撰寫,完整學習建議直接參見該書。

The Rust Programming Language

該書也有中文翻譯版,不過個人閱讀以英文原版為主以鞏固對 terminology 的一致認識,我認為對未來閱讀以及查找資料會較為順暢。

不論語言該書都是相當優秀的學習資源,選擇適合你的語言開始學習 Rust 吧。

Appendix F: Translations of the Book

列舉 Enums

enum 定義的是可能的一組特定數值,以下定義一組顏色。

enum Color {
    Green,
    Red,
    Blue
}

struct Tag {
    name: String,
    color: Color
}

使用時則是使用 :: 來取得特定顏色。

let tag1 = Tag {
    name: String::from("new"),
    color: Color::Blue
};

enum 中的 variants 也能夠擁有數值,以下讓 Color 都能有擁有一個 String

enum Color {
    Green(String),
    Red(String),
    Blue(String)
}

fn main() {
    let c1 = Color::Blue(String::from("Blue"));
}

這種做法可以讓 enum 中不同的 variant 有不同的值。

enum Color {
    Green(String),
    Red(i32),
    Blue(f64)
}

fn main() {
    let c1 = Color::Green(String::from("Blue"));
    let c2 = Color::Red(5);
    let c3 = Color::Blue(1.0);
}

enum 像 struct 一樣可以有 impl block 定義 methods。

#[derive(Debug)]
enum Color {
    Green(String),
    Red(String),
    Blue(String)
}

impl Color {
    fn print(&self) {
        println!("{:?}", self)
    }
}

fn main() {
    let c1 = Color::Blue(String::from("Blue"));
    c1.print();     // Blue("Blue")
}

Option

Option 是一種內建在 Rust 的 standard library 的 enum,他是 Rust 處理值可能為空值的解決方案,因為 Rust 不像其他許多語言有空值的型別,如 Javascript 的 nullundefined。

以下是 Option 的定義:

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

看起來非常的 self explained,Option 代表的是可能為空 None、或可能有某些數值 Some(T)。這邊的 T 是 generic,也就是泛型,之後會學習到。

Result

還有另一種特定的 enum 叫做 Result,與 Option 有些類似,這是他的定義:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

在某些可能會失敗的函式中會使用 Result 作為回傳值 (比如讀取檔案),讓我們 handle success 和 error case。

模式配對 Pattern matching

Pattern matching 是讓 Rust 的 enum 真正強大的地方,它是一種流程控制,有點像其他語言中的 switch case。

下面的程式是一個簡短的 pattern matching 的範例,我們使用 match 進行 pattern matching,為每個 variant 配對一段 expression,l 在進入 match 之後比對各個 variant 然後執行配對到的程式。

enum Language {
    Chinese,
    English,
    Japanese
}

fn main() {
    let l = Language::Japanese;
    
    match l {
        Language::Chinese => println!("你好"),
        Language::English => println!("Hello"),
        Language::Japanese => println!("こんにちは")
    };   

    // output: こんにちは
}

當 enum 有綁定值時可以使用以下方式取得值。

enum Language {
    Chinese(String),
    English(String),
    Japanese(String)
}

fn main() {
    let l = Language::English(String::from("Josh"));
    
    match l {
        Language::Chinese(s) => println!("你好 {}", s),
        Language::English(s) => println!("Hello {}", s),
        Language::Japanese(s) => println!("こんにちは {}", s)
    };

    // output: Hello Josh
}

pattern matching 必須涵蓋所有可能的 variant,若有缺少會直接報錯。

enum Language {
    Chinese(String),
    English(String),
    Japanese(String)
}

fn main() {
    let l = Language::English(String::from("Josh"));
    
    match l {
        Language::Chinese(s) => println!("你好 {}", s),
        Language::English(s) => println!("Hello {}", s),
    };

    // ERROR: non-exhaustive patterns: `Language::Japanese(_)` not covered
}

如果只關心部分的可能性,又不想把全部列出來,可以使用 other 變數做 catch-all 的操作。

#[derive(Debug)]
enum Language {
    Chinese(String),
    English(String),
    Japanese(String)
}

fn main() {
    let l = Language::English(String::from("Josh"));
    
    match l {
        Language::Chinese(s) => println!("你好 {}", s),
        other => println!("{:?}", other)
    };

    // output: English("Josh")
}

如果也不需要取得變數,可以用 _ 替代 other

enum Language {
    Chinese(String),
    English(String),
    Japanese(String)
}

fn main() {
    let l = Language::English(String::from("Josh"));
    
    match l {
        Language::Chinese(s) => println!("你好 {}", s),
        _ => println!("Hello")
    };
}

甚至如果只關心部分的可能而其他想要直接忽略,可以用空的 tuple (),這樣就什麼都不會發生。

enum Language {
    Chinese(String),
    English(String),
    Japanese(String)
}

fn main() {
    let l = Language::English(String::from("Josh"));
    
    match l {
        Language::Chinese(s) => println!("你好 {}", s),
        _ => ()
    };
}

像上面這種情況可以用 if let 語法做更精簡的改寫。

if let

當我們只關心其中一種情況,其他所有情況都想忽略時,if let 語法提供了更簡單的寫法。

enum Language {
    Chinese(String),
    English(String),
    Japanese(String)
}

fn main() {
    let l = Language::English(String::from("Josh"));
    
    if let Language::English(s) = l {
        println!("Hello {}", s);
    }

    // output: Hello Josh
}

就像單純的 if 一樣,if let 也可以加上 else 區塊,就相當於 pattern matching 中 _ 的區塊。

enum Language {
    Chinese(String),
    English(String),
    Japanese(String)
}

fn main() {
    let l = Language::Chinese(String::from("Josh"));
    
    if let Language::English(s) = l {
        println!("Hello {}", s);
    } else {
        println!("Hello world!");
    }

    // // output: Hello world!
}

寫在最後

The book 中下一章是關於套件管理、程式碼拆分與管理專案結構,這部分很重要不過我習慣在實戰中慢慢邊參考邊練習,這邊稍微去閱讀就好,筆記會跳過這章下一篇直接進入集合。