在開發程式的時候,我們很常需要產生出有獨立名稱,且為連續或是有規律的數值,用來當作介面的參數數值。舉例來說,有一個函數擁有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行會編譯失敗。需將程式修改成:
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」是筆者開發的程序式巨集套件,可以讓列舉實作Oridinal
特性,使其擁有from_ordinal_unsafe
、from_ordinal
關聯函數,以及ordinal
方法,還有VARIANT_COUNT
、VARIANTS
、VALUES
常數。
Crates.io
Cargo.toml
使用方法
使用#[derive(Ordinalize)]
屬性可以讓只有單元變體(Unit Variant)的列舉實作Ordinalize
特性。
use enum_ordinalize::Ordinalize; | |
enum MyEnum { | |
Zero, | |
One, | |
Two, | |
} | |
assert_eq!(3, MyEnum::VARIANT_COUNT); | |
assert_eq!([MyEnum::Zero, MyEnum::One, MyEnum::Two], MyEnum::VARIANTS); | |
assert_eq!([0i8, 1i8, 2i8], MyEnum::VALUES); | |
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) }); |
「序數」的大小
序數的大小是由列舉自身的大小來決定的。列舉的變體所表示的值愈大(若為負數則是愈小),則列舉的大小愈大。
例如:
use enum_ordinalize::Ordinalize; | |
enum MyEnum { | |
Zero, | |
One, | |
Two, | |
Thousand = 1000, | |
} | |
assert_eq!(4, MyEnum::VARIANT_COUNT); | |
assert_eq!([MyEnum::Zero, MyEnum::One, MyEnum::Two, MyEnum::Thousand], MyEnum::VARIANTS); | |
assert_eq!([0i16, 1i16, 2i16, 1000i16], MyEnum::VALUES); | |
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) }); |
為了要儲存1000
,MyEnum
列舉的大小會增長,序數的大小會變成i16
,而不是i8
。
您也可以使用#[repr(型別)]
屬性來明確控制大小。舉例來說:
use enum_ordinalize::Ordinalize; | |
enum MyEnum { | |
Zero, | |
One, | |
Two, | |
Thousand = 1000, | |
} | |
assert_eq!(4, MyEnum::VARIANT_COUNT); | |
assert_eq!([MyEnum::Zero, MyEnum::One, MyEnum::Two, MyEnum::Thousand], MyEnum::VARIANTS); | |
assert_eq!([0usize, 1usize, 2usize, 1000usize], MyEnum::VALUES); | |
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) }); |
好用的自動累進
變體所代表的整數是連續自動累進的,我們也可以在累進的同時,於任意的變體上設定明確的數值。
use enum_ordinalize::Ordinalize; | |
enum MyEnum { | |
Two = 2, | |
Three, | |
Four, | |
Eight = 8, | |
Nine, | |
NegativeTen = -10, | |
NegativeNine, | |
} | |
assert_eq!(7, MyEnum::VARIANT_COUNT); | |
assert_eq!([MyEnum::Two, MyEnum::Three, MyEnum::Four, MyEnum::Eight, MyEnum::Nine, MyEnum::NegativeTen, MyEnum::NegativeNine], MyEnum::VARIANTS); | |
assert_eq!([2i8, 3i8, 4i8, 8i8, 9i8, -10i8, -9i8], MyEnum::VALUES); | |
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) }); |
在列舉本身實作功能
基於某些情形,如果我們不想要替我們的列舉實作Ordinalize
特性,也可以選擇禁用特性的實作,逐一設定有用到的常數和函數。函數會是常數函數。名稱和存取權限也都可以由我們來定義。
use enum_ordinalize::Ordinalize; | |
enum MyEnum { | |
A, | |
B, | |
} | |
assert_eq!(2, MyEnum::VARIANT_COUNT); | |
assert_eq!([MyEnum::A, MyEnum::B], MyEnum::VARIANTS); | |
assert_eq!([0i8, 1i8], MyEnum::VALUES); | |
assert_eq!(0i8, MyEnum::A.ordinal()); | |
assert_eq!(1i8, MyEnum::B.ordinal()); | |
assert_eq!(Some(MyEnum::A), MyEnum::from_ordinal(0i8)); | |
assert_eq!(Some(MyEnum::B), MyEnum::from_ordinal(1i8)); | |
assert_eq!(MyEnum::A, unsafe { MyEnum::from_ordinal_unsafe(0i8) }); | |
assert_eq!(MyEnum::B, unsafe { MyEnum::from_ordinal_unsafe(1i8) }); |