枚举
Syntax
Enumeration →
enum IDENTIFIER GenericParams? WhereClause? { EnumVariants? }
EnumVariants → EnumVariant ( , EnumVariant )* ,?
EnumVariant →
OuterAttribute* Visibility?
IDENTIFIER ( EnumVariantTuple | EnumVariantStruct )? EnumVariantDiscriminant?
EnumVariantTuple → ( TupleFields? )
EnumVariantStruct → { StructFields? }
枚举 (也称为 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; // 常量项的路径表达式。
let x = UnitLike {}; // 结构体表达式。
let y = TupleLike(123); // 调用表达式。
let y = TupleLike { 0: 123 }; // 使用整数字段名称的结构体表达式。
let z = StructLike { value: 123 }; // 结构体表达式。
}
判别值
每个枚举实例都有一个 判别值 :逻辑上与之关联的整数,用于确定它持有哪个变体。
在 Rust 表示 下,判别值被解释为一个 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
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,但那会导致枚举溢出。
}
}
访问判别值
通过 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; // 类型不匹配错误
}
变体可见性
枚举变体在语法上允许使用 可见性 注解,但在验证枚举时会被拒绝。这允许项在使用的不同上下文中以统一的语法进行解析。
#![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 }
}
}