Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

枚举

枚举(enumeration),也称 enum,是具名枚举类型以及一组构造器的同时定义,这些构造器可用于创建或模式匹配相应枚举类型的值。

枚举使用 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 简称枚举变体。

没有构造器包含字段的枚举称为*无字段枚举*。例如,这是一个无字段枚举:

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

如果无字段枚举只包含单元变体,则该枚举称为*纯单元枚举*。例如:

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

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

每个变体在类型命名空间中定义其类型,尽管该类型不能用作类型说明符。类元组和类单元变体还在值命名空间中定义一个构造器。

类结构体变体可以用结构体表达式实例化。

类元组变体可以用调用表达式结构体表达式实例化。

类单元变体可以用路径表达式结构体表达式实例化。例如:

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

use Examples::*; // 创建所有变体的别名。
let x = UnitLike; // const 项的路径表达式。
let x = UnitLike {}; // 结构体表达式。
let y = TupleLike(123); // 调用表达式。
let y = TupleLike { 0: 123 }; // 使用整数字段名的结构体表达式。
let z = StructLike { value: 123 }; // 结构体表达式。
}

判别值

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

Rust 表示法下,判别值被解释为 isize 值。但是,编译器允许在实际内存布局中使用更小的类型(或其他区分变体的方式)。

指定判别值

显式判别值

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

  1. 如果枚举是“纯单元枚举“。
  1. 如果使用了原始表示法。例如:

    #![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(与上一个冲突!)
}
}

当未指定的判别值的前一个判别值是该判别值大小的最大值时,也是错误的。

#![allow(unused)]
fn main() {
#[repr(u8)]
enum OverflowingDiscriminantError {
    Max = 255,
    MaxPlusOne, // 将会是 256,但这会导致枚举溢出。
}

#[repr(u8)]
enum OverflowingDiscriminantError2 {
    MaxMinusOne = 254, // 254
    Max,               // 255
    MaxPlusOne,        // 将会是 256,但这会导致枚举溢出。
}
}

显式枚举判别值初始化器不能使用来自封闭枚举的泛型参数。

#![allow(unused)]
fn main() {
#[repr(u32)]
enum E<'a, T, const N: u32> {
    Lifetime(&'a T) = {
        let a: &'a (); // 错误。
        1
    },
    Type(T) = {
        let x: T; // 错误。
        2
    },
    Const = N, // 错误。
}
}

访问判别值

通过 mem::discriminant

std::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 FieldlessWithDiscriminants {
    First = 10,
    Tuple(),
    Second = 20,
    Struct{},
    Unit,
}

assert_eq!(10, FieldlessWithDiscriminants::First as u8);
assert_eq!(11, FieldlessWithDiscriminants::Tuple() as u8);
assert_eq!(20, FieldlessWithDiscriminants::Second as u8);
assert_eq!(21, FieldlessWithDiscriminants::Struct{} as u8);
assert_eq!(22, FieldlessWithDiscriminants::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 {}
}

零变体枚举等价于 never 类型,但不能强制转换为其他类型。

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

变体可见性

枚举变体在语法上允许 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 },
        }
    }
}

// 空的 `vis` 是允许的。
mac_variant! { E }

// 这是允许的,因为它在被验证之前就被移除了。
#[cfg(false)]
enum E {
    pub U,
    pub(crate) T(u8),
    pub(super) T { f: String },
}
}