枚举
语法
枚举 :
enum
标识符 泛型参数组? Where从句?{
枚举条目组?}
枚举条目组 :
枚举条目 (,
枚举条目 )*,
?枚举条目 :
外围属性* 可见性?
标识符 ( 枚举条目元组 | 枚举条目结构体 )? 枚举条目判别值?枚举条目元组 :
(
元组字段组?)
枚举条目结构体 :
{
结构体字段组?}
枚举条目判别值 :
=
表达式
一个 枚举类型 , 简称枚举 enum ,是一种同时定义了枚举类型和一组 构造器 的具名类型。 这些构造器可以用来创建或者匹配相应枚举类型的值。
枚举类型使用关键字 enum
进行声明。
下面是一个 enum
的使用示例:
#![allow(unused)] fn main() { enum Animal { Dog, Cat, } let mut a: Animal = Animal::Dog; a = Animal::Cat; }
枚举构造器可以拥有命名的字段或者没有命名的字段:
#![allow(unused)] fn main() { enum Animal { Dog(String, f64), Cat { name: String, weight: f64 }, } let mut a: Animal = Animal::Dog("Cocoa".to_string(), 37.2); a = Animal::Cat { name: "Spotty".to_string(), weight: 2.7 }; }
在这个例子中, Cat
是一个 类结构体枚举变体 ,而 Dog
则仅称为枚举变体。
没有构造器包含字段的枚举称为 field-less enum "无字段枚举" 。例如,以下是一个无字段枚举:
#![allow(unused)] fn main() { enum Fieldless { Tuple(), Struct{}, Unit, } }
如果一个不包含字段的枚举只包含单元枚举变体,则该枚举称为 unit-only enum "单元枚举"。例如:
#![allow(unused)] fn main() { enum Enum { Foo = 3, Bar = 2, Baz = 1, } }
判别值
每个枚举实例都有一个 判别值 :一个逻辑上与之关联的整数,用来确定它持有的变体。
在 默认表示 下,判别值解释为 isize
值。
然而,编译器允许在实际内存布局中使用较小的类型 (或其他区分变体的方式) 。
分配判别值
显式判别值
在两种情况下,可以通过在变体名称后跟 =
和 常量表达式 来明确设置变体的判别值 :
-
如果该枚举是 "单元枚举" 。
-
如果使用 原始表示 。例如:
#![allow(unused)] fn main() { #[repr(u8)] enum Enum { Unit = 3, Tuple(u16), Struct { a: u8, b: u16, } = 1, } }
隐式判别值
如果枚举变体的判别值没有指定,则它被设置为在声明中前一个变体的判别值加 1 。如果第一个变体的判别值未指定,则设置为零。
#![allow(unused)] fn main() { enum Foo { Bar, // 0 第一个未指定,则为0 Baz = 123, // 123 Quux, // 124 未指定,相比前一个加1 } let baz_discriminant = Foo::Baz as u32; assert_eq!(baz_discriminant, 123); }
限制条件
当两个变体判别值相同时,是一个错误。
#![allow(unused)] fn main() { enum SharedDiscriminantError { SharedA = 1, SharedB = 1 } enum SharedDiscriminantError2 { Zero, // 0 One, // 1 OneToo = 1 // 1 (collision with previous!) } }
如果先前枚举的判别值达到了其类型能够表示的最大值,那么下一个没有指定判别值的项将导致错误。
#![allow(unused)] fn main() { #[repr(u8)] enum OverflowingDiscriminantError { Max = 255, MaxPlusOne // 将是 256 ,但那会使枚举溢出。 } #[repr(u8)] enum OverflowingDiscriminantError2 { MaxMinusOne = 254, // 254 Max, // 255 MaxPlusOne // 将是 256 ,但那会使枚举溢出。 } }
访问判别值
通过 mem::discriminant
mem::discriminant
返回一个不透明的引用,指向枚举值的判别值,可以进行比较。但不能用于获取判别值。
转换
如果一个枚举类型是 单元枚举 ,那么它的判别值可以通过 数字强转 直接访问;比如:
#![allow(unused)] fn main() { enum Enum { Foo, Bar, Baz, } assert_eq!(0, Enum::Foo as isize); assert_eq!(1, Enum::Bar as isize); assert_eq!(2, Enum::Baz as isize); }
无成员的枚举 可以被强制类型转换,如果它们没有显式的判别值,或者只有单元变体是显式的。
#![allow(unused)] fn main() { enum Fieldless { Tuple(), Struct{}, Unit, } assert_eq!(0, Fieldless::Tuple() as isize); assert_eq!(1, Fieldless::Struct{} as isize); assert_eq!(2, Fieldless::Unit as isize); #[repr(u8)] enum FieldlessWithDiscrimants { First = 10, Tuple(), Second = 20, Struct{}, Unit, } assert_eq!(10, FieldlessWithDiscrimants::First as u8); assert_eq!(11, FieldlessWithDiscrimants::Tuple() as u8); assert_eq!(20, FieldlessWithDiscrimants::Second as u8); assert_eq!(21, FieldlessWithDiscrimants::Struct{} as u8); assert_eq!(22, FieldlessWithDiscrimants::Unit as u8); }
指针转换
如果枚举指定了 原始表示 ,那么可以通过非安全的指针转换来可靠地访问判别值:
#![allow(unused)] fn main() { #[repr(u8)] enum Enum { Unit, Tuple(bool), Struct{a: bool}, } impl Enum { fn discriminant(&self) -> u8 { unsafe { *(self as *const Self as *const u8) } } } let unit_like = Enum::Unit; let tuple_like = Enum::Tuple(true); let struct_like = Enum::Struct{a: false}; assert_eq!(0, unit_like.discriminant()); assert_eq!(1, tuple_like.discriminant()); assert_eq!(2, struct_like.discriminant()); }
零变体枚举
没有变体的枚举被称为 零变体枚举 。由于它们没有有效值,因此不能实例化。
#![allow(unused)] fn main() { enum ZeroVariants {} }
零变体枚举被认为是 永不类型 的等效形式,但是它们不能被强制转换为其他类型。
#![allow(unused)] fn main() { enum ZeroVariants {} let x: ZeroVariants = panic!(); let y: u32 = x; // 类型不匹配的错误 }
变体可见性
枚举变体在语法上允许使用 可见性 注释,但在验证枚举时,会被拒绝。 这使得可以在使用它们的不同上下文中使用统一的语法来解析条目。
#![allow(unused)] fn main() { macro_rules! mac_variant { ($vis:vis $name:ident) => { enum $name { $vis Unit, $vis Tuple(u8, u16), $vis Struct { f: u8 }, } } } // 空的 `vis` 是允许的。 mac_variant! { E } // 这是允许的,因为它是在验证之前已删除。 #[cfg(FALSE)] enum E { pub U, pub(crate) T(u8), pub(super) T { f: String } } }