列舉

YouTube 上觀看本章內容: Part 1, Part 2, Part 3Part 4

enum 是列舉(enumeration)的簡稱。它們看起來與結構體非常相似,但又有所不同。區別有:

  • 當你想要一個東西另一個東西時,使用struct
  • 當你想要一個東西另一個東西時,請使用 enum

所以,結構體是用於多個事物在一起,而列舉則是用於多個選擇在一起。

要宣告列舉時,寫下 enum,並用程式碼區塊將包含的選項用逗號分隔。就像 struct 一樣,最後一部分的逗號則可有可無。我們將建立一個名為 ThingsInTheSky 的列舉:

enum ThingsInTheSky {
    Sun,
    Stars,
}

fn main() {}

這是個列舉,因為你可以看到太陽星星:你必須選擇一個。這些叫做變體(variants)

// 建立兩個選擇的列舉
enum ThingsInTheSky {
    Sun,
    Stars,
}

// 有這個函式我們可以用i32來建立ThingsInTheSky。
fn create_skystate(time: i32) -> ThingsInTheSky {
    match time {
        6..=18 => ThingsInTheSky::Sun, // 介於6到18小時之間我們可以見到太陽
        _ => ThingsInTheSky::Stars, // 除此之外,我們可以見到星星
    }
}

// 有這個函式我們可以匹配到ThingsInTheSky的兩個選擇。
fn check_skystate(state: &ThingsInTheSky) {
    match state {
        ThingsInTheSky::Sun => println!("I can see the sun!"),
        ThingsInTheSky::Stars => println!("I can see the stars!")
    }
}

fn main() {
    let time = 8; // 這是 8 點鐘
    let skystate = create_skystate(time); // create_skystate回傳ThingsInTheSky
    check_skystate(&skystate); // 給它參考那麼它就能讀到變數skystate
}

印出 I can see the sun!

你也可以將資料新增到列舉中。

enum ThingsInTheSky {
    Sun(String), // 現在每個變體都有字串
    Stars(String),
}

fn create_skystate(time: i32) -> ThingsInTheSky {
    match time {
        6..=18 => ThingsInTheSky::Sun(String::from("I can see the sun!")), // 這裡寫下字串
        _ => ThingsInTheSky::Stars(String::from("I can see the stars!")),
    }
}

fn check_skystate(state: &ThingsInTheSky) {
    match state {
        ThingsInTheSky::Sun(description) => println!("{}", description), // 給字串命名為description那麼我們就能使用它
        ThingsInTheSky::Stars(n) => println!("{}", n), // 或你能命名成 n。或其它任何東西──它無關緊要
    }
}

fn main() {
    let time = 8; // 這是 8 點鐘
    let skystate = create_skystate(time); // create_skystate 回傳 ThingsInTheSky
    check_skystate(&skystate); // 給它參考那麼它就能讀到變數skystate
}

印出來的結果一樣:I can see the sun!

你也可以"匯入(import)"一個列舉,這樣你就不用打那麼多字了。下面這個例子裡,我們每次在匹配我們的 mood 時都要輸入 Mood::

enum Mood {
    Happy,
    Sleepy,
    NotBad,
    Angry,
}

fn match_mood(mood: &Mood) -> i32 {
    let happiness_level = match mood {
        Mood::Happy => 10, // 我們每次都要輸入 Mood::
        Mood::Sleepy => 6,
        Mood::NotBad => 7,
        Mood::Angry => 2,
    };
    happiness_level
}

fn main() {
    let my_mood = Mood::NotBad;
    let happiness_level = match_mood(&my_mood);
    println!("Out of 1 to 10, my happiness is {}", happiness_level);
}

印出的是 Out of 1 to 10, my happiness is 7。讓我們匯入,這樣我們就可以少打點字了。要匯入所有的東西時寫做 *。注意:它和反參考關鍵字的 * 一樣,但完全不同。

enum Mood {
    Happy,
    Sleepy,
    NotBad,
    Angry,
}

fn match_mood(mood: &Mood) -> i32 {
    use Mood::*; // 我們匯入Mood裡的所有東西。現在我們可以只寫Happy、Sleepy等變體名。
    let happiness_level = match mood {
        Happy => 10, // 我們不用再寫 Mood:: 了
        Sleepy => 6,
        NotBad => 7,
        Angry => 2,
    };
    happiness_level
}

fn main() {
    let my_mood = Mood::Happy;
    let happiness_level = match_mood(&my_mood);
    println!("Out of 1 to 10, my happiness is {}", happiness_level);
}

enum 的一部分也可以轉變成整數。這是因為 Rust 給 enum 提供了以 0 開頭的數字給每個分支各自使用。如果你的列舉中沒有任何其他資料的話,你可以拿它來做些事情。

enum Season {
    Spring, // 如果這是 Spring(String) 或其它東西,它就不能這樣用
    Summer,
    Autumn,
    Winter,
}

fn main() {
    use Season::*;
    let four_seasons = vec![Spring, Summer, Autumn, Winter];
    for season in four_seasons {
        println!("{}", season as u32);
    }
}

印出:

0
1
2
3

不過如果你想的話,你也可以給它一個不同的數字──Rust 並不在意,可以用同樣的方式來使用它。只要在你想要有數值的變體加上 = 和數字。你不必給數字到所有變體。但如果你不這樣做,Rust 就會給變體從前一個分支數字加 1 的數字。

enum Star {
    BrownDwarf = 10,
    RedDwarf = 50,
    YellowStar = 100,
    RedGiant = 1000,
    DeadStar, // 想想看這個數字會有多少?
}

fn main() {
    use Star::*;
    let starvec = vec![BrownDwarf, RedDwarf, YellowStar, RedGiant];
    for star in starvec {
        match star as u32 {
            size if size <= 80 => println!("Not the biggest star."), // 記得: size 沒有任何意思。只不過是我們為了可以列印所選的名稱 
            size if size >= 80 => println!("This is a good-sized star."),
            _ => println!("That star is pretty big!"),
        }
    }
    println!("What about DeadStar? It's the number {}.", DeadStar as u32);
}

印出:

Not the biggest star.
Not the biggest star.
This is a good-sized star.
This is a good-sized star.
What about DeadStar? It's the number 1001.

DeadStar 本來是 4 號,但現在是 1001。

使用多種型別的列舉

你知道向量、陣列等等之中的元素都需要相同的型別(只有 tuple 不同)。但其實你可以用列舉來放不同的型別。想象一下,我們想要有個向量,有 u32i32。當然,你可以做出 Vec<(u32, i32)>(帶有 (u32, i32) 元組的向量),但是我們想要每次只有一種。所以這裡可以使用列舉。這是簡單的範例:

enum Number {
    U32(u32),
    I32(i32),
}

fn main() {}

所以這有兩個變體:U32 變體裡有 u32I32 變體裡有 i32U32I32 只是我們取的名字。它們可以取名叫 UThirtyTwoIThirtyTwo 或其他任何東西。

現在,如果我們把它們放到向量中,我們就會有 Vec<Number>,因為都是同一個型別編譯器會很開心。編譯器並不在乎我們有的是 u32 或者是 i32,因為它們都在一個叫做 Number 的單一型別裡面。因為它是列舉,你必須選擇一種,這就是我們想要的。我們將使用 .is_positive() 方法來挑選。如果是 true,那麼我們將選擇 U32,如果是 false,那麼我們將選擇 I32

現在程式碼像這樣:

enum Number {
    U32(u32),
    I32(i32),
}

fn get_number(input: i32) -> Number {
    let number = match input.is_positive() {
        true => Number::U32(input as u32), // 如果是正數改成 u32
        false => Number::I32(input), // 不然就給數字因為它已經是 i32
    };
    number
}


fn main() {
    let my_vec = vec![get_number(-800), get_number(8)];

    for item in my_vec {
        match item {
            Number::U32(number) => println!("It's a u32 with the value {}", number),
            Number::I32(number) => println!("It's an i32 with the value {}", number),
        }
    }
}

印出了我們想看到的結果:

It's an i32 with the value -800
It's a u32 with the value 8