在開發程式的時候,我們很常需要產生出有獨立名稱,且為連續或是有規律的數值,用來當作介面的參數數值。舉例來說,有一個函數擁有10種工作模式,可以透過該函數的第一個參數來傳入數值0~9來決定要使用哪一種模式,為了方便輸入,且為了確保輸入的數值是在0~9,我們通常會使用一種特殊結構作為這個參數的型別,來代入數值0~9。如果程式語言支援列舉(enum),這個功能通常會直接使用列舉來實現。但是Rust程式語言的列舉有個小缺點,那就是它不方便直接利用列舉變體的「序數」(ordinal),也就是那個變體在該列舉中被定義出來的順序,作為程式邏輯的應用。另外,如果要透過「序數」來取得對應的列舉變體,並沒有內建安全的作法。



內建取得列舉變體「序數」的作法

如果要得到某列舉變體的序數,必須要使用as關鍵字,直接將列舉實體轉型。程式如下:

enum Mode {
    Mode1,
    Mode2,
    Mode3,
}

fn main() {
    assert_eq!(2, Mode::Mode3 as u8);
}

但如果是列舉實體的參考,則無法使用as關鍵字來進行轉型。舉例來說:

enum Mode {
    Mode1,
    Mode2,
    Mode3,
}

struct Config {
    mode: Mode
}

fn foo(m: &Mode) {
    assert_eq!(2, m as u8);
}

fn main() {
    let config = Config {
        mode: Mode::Mode3
    };
    
    foo(&config.mode);
}

以上程式,第12行會編譯失敗。需將程式修改成:

#[derive(Clone)]
enum Mode {
    Mode1,
    Mode2,
    Mode3,
}

struct Config {
    mode: Mode
}

fn foo(m: Mode) {
    assert_eq!(2, m as u8);
}

fn main() {
    let config = Config {
        mode: Mode::Mode3
    };
    foo(config.mode.clone());
}

所以說這個方式並不太好用。

Enum Ordinalize

「Enum Ordinalize」是筆者開發的程序式巨集套件,可以讓列舉擁有from_ordinal_unsafefrom_ordinalvariantsvariant_count關聯函數,以及ordinal方法。

Crates.io

https://crates.io/crates/enum-ordinalize

Cargo.toml

enum-ordinalize = "*"

Ordinalize

使用#[derive(Ordinalize)]屬性可以讓只有單元變體(Unit Variant)的列舉擁有from_ordinal_unsafefrom_ordinalvariantsvariant_count關聯函數,以及ordinal方法。

#[macro_use]
extern crate enum_ordinalize;

#[derive(Debug, PartialEq, Eq, Ordinalize)]
enum MyEnum {
    Zero,
    One,
    Two,
}

assert_eq!(0i8, MyEnum::Zero.ordinal());
assert_eq!(1i8, MyEnum::One.ordinal());
assert_eq!(2i8, MyEnum::Two.ordinal());

assert_eq!(Some(MyEnum::Zero), MyEnum::from_ordinal(0i8));
assert_eq!(Some(MyEnum::One), MyEnum::from_ordinal(1i8));
assert_eq!(Some(MyEnum::Two), MyEnum::from_ordinal(2i8));

assert_eq!(MyEnum::Zero, unsafe { MyEnum::from_ordinal_unsafe(0i8) });
assert_eq!(MyEnum::One, unsafe { MyEnum::from_ordinal_unsafe(1i8) });
assert_eq!(MyEnum::Two, unsafe { MyEnum::from_ordinal_unsafe(2i8) });
取得變體
#[macro_use]
extern crate enum_ordinalize;

#[derive(Debug, PartialEq, Eq, Ordinalize)]
enum MyEnum {
    Zero,
    One,
    Two,
}

assert_eq!([MyEnum::Zero, MyEnum::One, MyEnum::Two], MyEnum::variants());
assert_eq!(3, MyEnum::variant_count());

variantsvariant_count是常數函數(Constant Function)。

「序數」的大小

序數的大小是由列舉自身的大小來決定的。列舉的變體所表示的值愈大(若為負數則是愈小),則列舉的大小愈大。

例如:

#[macro_use]
extern crate enum_ordinalize;

#[derive(Debug, PartialEq, Eq, Ordinalize)]
enum MyEnum {
    Zero,
    One,
    Two,
    Thousand = 1000,
}

assert_eq!(0i16, MyEnum::Zero.ordinal());
assert_eq!(1i16, MyEnum::One.ordinal());
assert_eq!(2i16, MyEnum::Two.ordinal());

assert_eq!(Some(MyEnum::Zero), MyEnum::from_ordinal(0i16));
assert_eq!(Some(MyEnum::One), MyEnum::from_ordinal(1i16));
assert_eq!(Some(MyEnum::Two), MyEnum::from_ordinal(2i16));

assert_eq!(MyEnum::Zero, unsafe { MyEnum::from_ordinal_unsafe(0i16) });
assert_eq!(MyEnum::One, unsafe { MyEnum::from_ordinal_unsafe(1i16) });
assert_eq!(MyEnum::Two, unsafe { MyEnum::from_ordinal_unsafe(2i16) });

為了要儲存1000MyEnum列舉的大小會增長,序數的大小會變成i16,而不是i8

您也可以使用#[repr(型別)]屬性來明確控制大小。舉例來說:

#[macro_use]
extern crate enum_ordinalize;

#[derive(Debug, PartialEq, Eq, Ordinalize)]
#[repr(usize)]
enum MyEnum {
    Zero,
    One,
    Two,
    Thousand = 1000,
}

assert_eq!(0usize, MyEnum::Zero.ordinal());
assert_eq!(1usize, MyEnum::One.ordinal());
assert_eq!(2usize, MyEnum::Two.ordinal());

assert_eq!(Some(MyEnum::Zero), MyEnum::from_ordinal(0usize));
assert_eq!(Some(MyEnum::One), MyEnum::from_ordinal(1usize));
assert_eq!(Some(MyEnum::Two), MyEnum::from_ordinal(2usize));

assert_eq!(MyEnum::Zero, unsafe { MyEnum::from_ordinal_unsafe(0usize) });
assert_eq!(MyEnum::One, unsafe { MyEnum::from_ordinal_unsafe(1usize) });
assert_eq!(MyEnum::Two, unsafe { MyEnum::from_ordinal_unsafe(2usize) });

好用的自動累進

變體所代表的整數是連續自動累進的,我們也可以在累進的同時,於任意的變體上設定明確的數值。

#[macro_use]
extern crate enum_ordinalize;

#[derive(Debug, PartialEq, Eq, Ordinalize)]
enum MyEnum {
    Two   = 2,
    Three,
    Four,
    Eight = 8,
    Nine,
    NegativeTen = -10,
    NegativeNine,
}

assert_eq!(4i8, MyEnum::Four.ordinal());
assert_eq!(9i8, MyEnum::Nine.ordinal());
assert_eq!(-9i8, MyEnum::NegativeNine.ordinal());

assert_eq!(Some(MyEnum::Four), MyEnum::from_ordinal(4i8));
assert_eq!(Some(MyEnum::Nine), MyEnum::from_ordinal(9i8));
assert_eq!(Some(MyEnum::NegativeNine), MyEnum::from_ordinal(-9i8));

assert_eq!(MyEnum::Four, unsafe { MyEnum::from_ordinal_unsafe(4i8) });
assert_eq!(MyEnum::Nine, unsafe { MyEnum::from_ordinal_unsafe(9i8) });
assert_eq!(MyEnum::NegativeNine, unsafe { MyEnum::from_ordinal_unsafe(-9i8) });