跳转到内容

枚举类型

自语言版本 2.0 起

枚举类型与结构体类型相似,但支持定义数据布局的多种变体.每个变体都有其独特的字段集合.枚举变体可在表达式,测试工具,匹配和解构中使用.

枚举类型声明会列出不同变体的数量,如下例所示:

enum Shape {
Circle{radius: u64},
Rectangle{width: u64, height: u64}
}

枚举变体可以有零个或多个字段.如果没有参数,大括号也可以省略,声明为简单值:

enum Color {
Red, Blue, Green
}

与结构体类型类似,枚举类型也可以具备能力.例如,Color 枚举类型可以声明为可复制,可丢弃和可存储的,就像原始数字类型:

enum Color has copy, drop, store, key { Red, Blue, Green }

枚举类型也可以拥有 key 能力,并作为全局存储中的数据根.在此上下文中,枚举的一个常见用途是数据版本控制:

enum VersionedData has key {
V1{name: String}
V2{name: String, age: u64}
}

与结构体类似,枚举类型可以是泛型的,并接受位置参数.例如,下面的类型表示一个泛型结果类型,其中变体构造函数使用位置参数而非命名参数(另见位置结构体).

enum Result<T> has copy, drop, store {
Err(u64),
Ok(T)
}

枚举值的构造方式与结构体值类似:

let s: String;
let data = VersionedData::V1{name: s};

如果枚举变体没有字段,大括号也可以省略:

let color = Color::Blue;

枚举的变体名称需要通过枚举类型名称来限定,如 VersionedData::V1.

注意:目前不支持通过 use 子句为枚举变体设置别名,但将在后续语言版本中添加

在某些情况下(如下面的匹配表达式),Move 编译器可以从上下文中推断出枚举类型,此时可以省略类型名称限定:

fun f(data: VersionedData) {
match (data) { V1{..} => .., ..} // 简单的变体名称即可
}

可以使用匹配表达式来检查枚举值.例如:

fun area(self: &Shape): u64 {
match (self) {
Circle{radius} => mul_with_pi(*radius * *radius),
Rectangle{width, height} => *width * *height
}
}

注意上面的匹配值是对枚举值的不可变引用.匹配表达式也可以消耗一个值,或通过可变引用来匹配以进行内部更新:

fun scale_radius(self: &mut Shape, factor: u64) {
match (self) {
Circle{radius: r} => *r = *r * factor,
_ => {} // 如果不是 Circle 则不做任何操作
}
}
```匹配表达式中的模式会按照文本出现的顺序依次求值,直到找到匹配项。如果未覆盖所有已知模式,将产生编译时错误。
模式可以嵌套并包含条件,如下例所示:
```move
let r : Result<Result<u64>> = Ok(Err(42));
let v = match (r) {
Ok(Err(c)) if c < 42 => 0,
Ok(Err(c)) if c >= 42 => 1,
Ok(_) => 2,
_ => 3
};
assert!(v == 1);

注意上例中,最后一个匹配子句(_)同时覆盖了模式 Ok(Err(_))Err(_).虽然在运行时,前面的子句会匹配所有 c 值的 Ok(Err(c)),但由于条件限制,编译器无法确定所有情况都被覆盖:匹配表达式中的条件在覆盖率检查时不被考虑.因此前两个匹配子句不足以确保匹配完整性,需要额外子句来避免编译器报错.

使用 is 操作符可以检查枚举值是否属于特定变体:

let data: VersionedData;
if (data is VersionedData::V1) { .. }

该操作符允许指定以”|”分隔的变体列表.如果被测试表达式的类型已知,变体无需通过枚举名限定:

assert!(data is V1|V2);

可以直接从枚举值中选择字段.回顾版本化数据的定义:

enum VersionedData has key {
V1{name: String}
V2{name: String, age: u64}
}

可以编写如下代码直接选择变体字段:

let s: String;
let data1 = VersionedData::V1{name: s};
let data2 = VersionedData::V2{name: s, age: 20};
assert!(data1.name == data2.name)
assert!(data2.age == 20);

注意当枚举值没有对应字段的变体时(如 data1.age),字段选择会中止.该情况使用的中止码为 0xCA26CBD9BE0B0001.根据 std::error 规范,此代码的类别为 std::error::INTERNAL,原因为 1.

仅当字段在所有变体中具有唯一名称和类型时才能进行选择.因此以下代码会产生编译时错误:

enum VersionedData has key {
V1{name: String}
V2{name: u64}
}
data.name
// ^^^^^ 编译时错误:`name` 字段选择存在歧义

枚举变体模式可用于 let 语句:

let data: VersionData;
let V1{name} = data;

如果变体不符合预期,解包枚举值会中止.为确保处理所有枚举变体,建议使用 match 表达式而非 let.match 在编译时检查确保覆盖所有变体.某些情况下,可使用 Move Prover 等工具验证 let 不会发生意外中止.

与结构体类似,可以通过显式解包来销毁枚举值.枚举可通过以下方式解包:

  • match 表达式中的模式匹配
  • let 绑定中的枚举模式
  • 赋值语句中的枚举模式移动
// 注意:`Shape` 没有 `drop` 能力,因此必须通过显式解构来销毁。
enum Shape {
Circle{radius: u64},
Rectangle{width: u64, height: u64}
}
fun destroy_empty(self: Shape) {
match (self) {
Shape::Circle{radius} => assert!(radius == 0),
Shape::Rectangle{width, height: _} => assert!(width == 0),
}
}
fun example_destroy_shapes() {
let c = Shape::Circle{radius: 0};
let r = Shape::Rectangle{width: 0, height: 0};
c.destroy_empty();
r.destroy_empty();
}

当且仅当新类型仅在变体列表末尾添加新变体时,枚举类型可以被另一个枚举类型升级.旧枚举类型中的所有变体都必须出现在新类型中,且顺序必须保持一致并从开头开始排列.考虑 VersionedData 类型,它最初可能只有一个版本:

enum VersionedData has key {
V1{name: String}
}

该类型可以升级到本文目前使用的版本:

enum VersionedData has key {
V1{name: String}
V2{name: String, age: u64}
}

以下升级将不被允许,因为必须保持变体顺序:

enum VersionedData has key {
V2{name: String, age: u64} // 非兼容性升级
V1{name: String}
}