枚举

语法
枚举 :
   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 值。 然而,编译器允许在实际内存布局中使用较小的类型 (或其他区分变体的方式) 。

分配判别值

显式判别值

在两种情况下,可以通过在变体名称后跟 =常量表达式 来明确设置变体的判别值 :

  1. 如果该枚举是 "单元枚举" 。

  2. 如果使用 原始表示 。例如:

    #![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 }
}
}