枚举

语法
枚举 :
   enum 标识符(IDENTIFIER)  泛型参数(GenericParams)? Where 从句(WhereClause)? { 枚举项(EnumItems)? }

枚举项(EnumItems) :
   枚举项(EnumItem) ( , 枚举项(EnumItem) )* ,?

枚举项(EnumItem) :
   外部属性(OuterAttribute)* 可见性(Visibility)?
   标识符(IDENTIFIER) ( 元组式枚举项(EnumItemTuple) | 结构体式枚举项(EnumItemStruct) )? 枚举项判别式(EnumItemDiscriminant)?

元组式枚举项(EnumItemTuple) :
   ( 元组字段(TupleFields)? )

结构体式枚举项(EnumItemStruct) :
   { 结构体字段(StructFields)? }

枚举项判别式(EnumItemDiscriminant) :
   = 表达式(Expression)

枚举(enumeration),也称为 enum,同时定义了一个名义上的枚举类型(enumerated type)和一组可用于创建或模式匹配相应枚举类型值的构造器(constructor)

枚举使用关键字 enum 声明。

enum 声明在它所在的模块或块的类型命名空间(type namespace)中定义了枚举类型。

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 是一个结构体式枚举变体(struct-like enum variant),而 Dog 简单地称为枚举变体(enum variant)。

如果一个枚举的所有构造器都不包含字段,则称其为无字段枚举(field-less enum)。例如,这是一个无字段枚举

#![allow(unused)]
fn main() {
enum Fieldless {
    Tuple(),
    Struct{},
    Unit,
}
}

如果一个无字段枚举只包含单元变体(unit variants),则称其为仅单元枚举(unit-only enum)。例如

#![allow(unused)]
fn main() {
enum Enum {
    Foo = 3,
    Bar = 2,
    Baz = 1,
}
}

变体构造器(Variant constructors)类似于结构体(struct)定义,可以通过枚举名称的路径引用,包括在use 声明(use declarations)中。

每个变体都在类型命名空间(type namespace)中定义其类型,尽管该类型不能用作类型说明符。元组式(Tuple-like)和单元式(unit-like)变体也在值命名空间(value namespace)中定义了一个构造器。

结构体式变体(struct-like variant)可以使用结构体表达式(struct expression)实例化。

元组式变体(tuple-like variant)可以使用调用表达式(call expression)结构体表达式(struct expression)实例化。

单元式变体(unit-like variant)可以使用路径表达式(path expression)结构体表达式(struct expression)实例化。例如

#![allow(unused)]
fn main() {
enum Examples {
    UnitLike,
    TupleLike(i32),
    StructLike { value: i32 },
}

use Examples::*; // Creates aliases to all variants.
let x = UnitLike; // Path expression of the const item.
let x = UnitLike {}; // Struct expression.
let y = TupleLike(123); // Call expression.
let y = TupleLike { 0: 123 }; // Struct expression using integer field names.
let z = StructLike { value: 123 }; // Struct expression.
}

判别式

每个枚举实例都有一个判别式(discriminant):一个与之逻辑关联的整数,用于确定它持有哪个变体。

Rust 表示(Rust representation)下,判别式被解释为 isize 值。然而,编译器在其实际内存布局中可以使用更小的类型(或另一种区分变体的方式)。

分配判别式值

显式判别式

在两种情况下,可以通过在变体名称后加上 = 和一个常量表达式(constant expression)来显式设置变体的判别式

  1. 如果该枚举是“仅单元(unit-only)”的。
  1. 如果使用了原始表示(primitive representation)。例如

    #![allow(unused)]
    fn main() {
    #[repr(u8)]
    enum Enum {
        Unit = 3,
        Tuple(u16),
        Struct {
            a: u8,
            b: u16,
        } = 1,
    }
    }

隐式判别式

如果未指定变体的判别式,则将其设置为声明中前一个变体判别式的值加一。如果声明中第一个变体的判别式未指定,则将其设置为零。

#![allow(unused)]
fn main() {
enum Foo {
    Bar,            // 0
    Baz = 123,      // 123
    Quux,           // 124
}

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 // Would be 256, but that overflows the enum.
}

#[repr(u8)]
enum OverflowingDiscriminantError2 {
    MaxMinusOne = 254, // 254
    Max,               // 255
    MaxPlusOne         // Would be 256, but that overflows the enum.
}
}

访问判别式

通过 mem::discriminant

std::mem::discriminant 返回一个指向枚举值判别式的不透明引用,该引用可以用于比较。这不能用于获取判别式的值。

转型

如果一个枚举是仅单元(unit-only)的(没有元组和结构体变体),那么它的判别式可以使用数值转型(numeric cast)直接访问;例如

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

无字段枚举(Field-less enums)如果没有显式判别式,或者只有单元变体是显式的,则可以进行转型。

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

指针转型

如果枚举指定了原始表示(primitive representation),那么可以通过不安全的指针转型可靠地访问判别式

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

零变体枚举

零变体的枚举被称为零变体枚举(zero-variant enums)。由于它们没有有效的值,因此无法实例化。

#![allow(unused)]
fn main() {
enum ZeroVariants {}
}

零变体枚举等同于never 类型(never type),但它们不能被强制转换为其他类型。

#![allow(unused)]
fn main() {
enum ZeroVariants {}
let x: ZeroVariants = panic!();
let y: u32 = x; // mismatched type error
}

变体可见性

枚举变体在语法上允许可见性(Visibility)注解,但在验证枚举时会被拒绝。这使得可以在不同的使用上下文中以统一的语法解析项目。

#![allow(unused)]
fn main() {
macro_rules! mac_variant {
    ($vis:vis $name:ident) => {
        enum $name {
            $vis Unit,

            $vis Tuple(u8, u16),

            $vis Struct { f: u8 },
        }
    }
}

// Empty `vis` is allowed.
mac_variant! { E }

// This is allowed, since it is removed before being validated.
#[cfg(FALSE)]
enum E {
    pub U,
    pub(crate) T(u8),
    pub(super) T { f: String }
}
}