Enumerations

枚举

enumerations.md
commit: 4f32346c1ace326fe3d699f20bbd7a77680e830c
本章译文最后维护日期:2023-01-15

句法
Enumeration :
   enum IDENTIFIER  GenericParams? WhereClause? { EnumItems? }

EnumItem :
   OuterAttribute* Visibility?
   IDENTIFIER ( EnumItemTuple | EnumItemStruct )? EnumItemDiscriminant?

EnumItemTuple :
   ( TupleFields? )

EnumItemStruct :
   { StructFields? }

EnumItemDiscriminant :
   = Expression

枚举,英文为 enumeration,常见其简写形式 enum,它同时定义了一个标称型(nominal)枚举类型和一组构造器,这可用于创建或使用模式来匹配相应枚举类型的值。

枚举使用关键字 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 是一个类结构体枚举变体(struct-like enum variant),而 Dog 则被简单地称为枚举变体。 变体都不带字段的枚举被称为*无字段枚举*。比如下面这个就是无字段枚举:

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

如果无字段枚举枚举的变体全是单元体变体,这类枚举也被称为*单元体枚举*。比如:

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

Discriminants

判别值

每个枚举实例都有一个判别值:一个逻辑上与其关联的整数,用于确定它持有的是哪个变体。

默认表型下,判别值被解释为一个 isize 的值。然而,编译器允许在其实际内存布局中使用内存尺寸更小的类型(或其他区分变体的方法)。

Assigning discriminant values

指定判别值的值

Explicit discriminants

显式判别值

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

  1. 如果枚举变体全是"单元体".

  2. 如果使用了原语表型。例如:

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

Implicit discriminants

隐式判别值

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

#![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 (和前值冲突!)
}
}

当前一个变体的判别值是当前表形允许的的最大值时,再使用默认判别值就也是错误的。

#![allow(unused)]
fn main() {
#[repr(u8)]
enum OverflowingDiscriminantError {
    Max = 255,
    MaxPlusOne // 应该是256,但枚举溢出了
}

#[repr(u8)]
enum OverflowingDiscriminantError2 {
    MaxMinusOne = 254, // 254
    Max,               // 255
    MaxPlusOne         // 应该是256,但枚举溢出了。
}
}

Accessing discriminant

读取判别值

Via mem::discriminant

通过 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);
}

Pointer casting

指针转换

如果枚举指定应用了原语表型,则可以通过 unsafe指针转换访问判别值:

#![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类型等效,但它不能被强转为其他类型。

#![allow(unused)]
fn main() {
enum ZeroVariants {}
let x: ZeroVariants = panic!();
let y: u32 = x; // 类型不匹配错误
}

Variant visibility

变体的可见性

依照句法规则,枚举变体是允许有自己的[可见性(visibility)][Visibility]限定/注解(annotation)的,但当枚举被(句法分析程序)验证(validate)通过后,可见性注解又被弃用。因此,在源码解析层面,允许跨不同的上下文对其中不同类型的程序项使用统一的句法规则进行解析。

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