Skip to content
🎉 Welcome to the new Aptos Docs! Click here to submit an issue.

枚举类型

自语言版本 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 语句:

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

如果变体不符合预期,解包枚举值会中止。为确保处理所有枚举变体,建议使用 match 表达式而非 letmatch 在编译时检查确保覆盖所有变体。某些情况下,可使用 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}
}