news 2026/9/18 8:47:13

Aptos Move 结构体、资源与枚举完全指南:从定义、能力到可见性与模式匹配

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Aptos Move 结构体、资源与枚举完全指南:从定义、能力到可见性与模式匹配

Aptos Move 结构体、资源与枚举完全指南:从定义、能力到可见性与模式匹配

【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core

导读

本文以 Move 语言手册《Structs, Resources, and Enums》章节为骨架,系统讲解 Aptos Move 中用户自定义类型的三大家族:结构体(struct)、资源(resource)与枚举(enum)。你将掌握:如何在模块中定义与使用结构体、如何通过 abilities(能力)控制类型的复制、丢弃与全局存储行为、如何用资源模型保护链上资产(如 Coin)、以及 Move 2.0/2.4 引入的位置结构体、match 模式匹配、跨模块可见性修饰符等进阶特性。读完本文,你能够读懂并编写 Aptos 框架中真实的类型设计代码(如 coin.move 中的 Coin/CoinStore/CoinInfo),并为自己的智能合约选择正确的类型建模方案。


一、结构体(Struct)与资源(Resource)

1.1 核心概念:什么是结构体与资源

结构体(struct)是用户自定义的、包含类型化字段(typed fields)的数据结构。结构体可以存储任何非引用类型,包括其他结构体本身。

在 Move 中,"资源"(resource)并不是独立的语法关键字,而是对具有特定行为的结构体的称谓:当结构体的值**既不能被复制(copy)也不能被丢弃(drop)**时,我们就称之为资源。这样的资源值必须在函数结束时完成所有权转移,这一特性使资源非常适合用于:

  • 定义全局存储(global storage)的数据模式;
  • 表示重要数值(例如代币 token)。

默认的"线性 + 瞬态"模型:默认情况下,结构体是linear(线性)且ephemeral(瞬态)的,含义是:

  • 不能被复制(cannot be copied);
  • 不能被丢弃(cannot be dropped);
  • 不能被存储到全局存储(cannot be stored in global storage)。

这意味着所有值都必须转移所有权(线性),并且必须在程序执行结束前被处理完毕(瞬态)。通过为结构体赋予 abilities 能力,可以放宽这些约束,允许值被复制、被丢弃、被存入全局存储,或作为全局存储的数据模式。

1.2 定义结构体

结构体必须定义在模块内部

module 0x2::m { struct Foo { x: u64, y: bool } struct Bar {} struct Baz { foo: Foo, } // ^ 注意:允许尾随逗号 }

结构体不能递归,以下定义是非法的:

module 0x2::m { struct Foo { x: Foo } // ^ error! Foo 不能包含 Foo }

对于使用编号字段而非命名字段的"位置结构体"(positional structs),见下文位置结构体一节。

默认情况下,结构体声明是线性且瞬态的。要让值能配合某些操作(复制、丢弃、存入全局存储,或用作存储模式),可以通过has <ability>注解为结构体授予能力:

module 0x2::m { struct Foo has copy, drop { x: u64, y: bool } }

1.3 命名规则

结构体名称必须以大写字母AZ开头;首字母之后可以包含下划线_、小写字母az、大写字母AZ以及数字09

module 0x2::m { struct Foo {} struct BAR {} struct B_a_z_4_2 {} }

这条"必须以大写字母开头"的限制是为了给未来的语言特性留出空间,未来版本可能会移除或保留它。

1.4 使用结构体

创建(打包)结构体

通过写出结构体名称并跟每个字段的值来创建(或称"打包",packing)结构体值:

module 0x2::m { struct Foo has drop { x: u64, y: bool } struct Baz has drop { foo: Foo } fun example() { let foo = Foo { x: 0, y: false }; let baz = Baz { foo }; } }

如果字段名与局部变量名相同,可以使用字段名简写(field name punning):

module 0x2::m { fun example() { let baz = Baz { foo: foo }; // 等价于 let baz = Baz { foo }; } }
通过模式匹配销毁结构体

结构体值可以通过在let绑定或赋值中做模式匹配来销毁:

module 0x2::m { struct Foo { x: u64, y: bool } struct Bar { foo: Foo } struct Baz {} fun example_destroy_foo() { let foo = Foo { x: 3, y: false }; let Foo { x, y: foo_y } = foo; // ^ `x: x` 的简写 // 两个新绑定: x: u64 = 3, foo_y: bool = false } fun example_destroy_foo_wildcard() { let foo = Foo { x: 3, y: false }; let Foo { x, y: _ } = foo; // 只有 x 一个绑定(y 被通配符 _ 忽略) } fun example_destroy_foo_assignment() { let x: u64; let y: bool; Foo { x, y } = Foo { x: 3, y: false }; // 修改既有变量 x 与 y: x = 3, y = false } fun example_foo_ref() { let foo = Foo { x: 3, y: false }; let Foo { x, y } = &foo; // 两个新绑定: x: &u64, y: &bool } fun example_foo_ref_mut() { let foo = Foo { x: 3, y: false }; let Foo { x, y } = &mut foo; // 两个新绑定: x: &mut u64, y: &mut bool } fun example_destroy_bar() { let bar = Bar { foo: Foo { x: 3, y: false } }; let Bar { foo: Foo { x, y } } = bar; // ^ 嵌套模式 // 两个新绑定: x: u64 = 3, y: bool = false } fun example_destroy_baz() { let baz = Baz {}; let Baz {} = baz; } }
借用结构体与字段

&&mut运算符可以用来创建对结构体或其字段的引用。下面的示例包含一些可选的类型注解(如: &Foo)以展示操作的类型:

module 0x2::m { fun example() { let foo = Foo { x: 3, y: true }; let foo_ref: &Foo = &foo; let y: bool = foo_ref.y; // 通过结构体引用读取字段 let x_ref: &u64 = &foo.x; let x_ref_mut: &mut u64 = &mut foo.x; *x_ref_mut = 42; // 通过可变引用修改字段 } }

也可以借用嵌套结构体的内部字段:

module 0x2::m { fun example() { let foo = Foo { x: 3, y: true }; let bar = Bar { foo }; let x_ref = &bar.foo.x; } }

还可以通过结构体引用再借用其字段:

module 0x2::m { fun example() { let foo = Foo { x: 3, y: true }; let foo_ref = &foo; let x_ref = &foo_ref.x; // 效果等同于 let x_ref = &foo.x } }
读写字段

如果字段可复制,可以通过解引用(dereference)被借用的字段来读取并复制字段值:

module 0x2::m { fun example() { let foo = Foo { x: 3, y: true }; let bar = Bar { foo: copy foo }; let x: u64 = *&foo.x; let y: bool = *&foo.y; let foo2: Foo = *&bar.foo; } }

点运算符(dot operator)可以无需显式借用与解引用,直接读取并复制结构体的任意可复制字段:

module 0x2::m { fun example() { let foo = Foo { x: 3, y: true }; let x = foo.x; // x == 3 let y = foo.y; // y == true let bar = Bar { foo }; let foo2: Foo = *&bar.foo; // `Foo` 必须可复制 let foo3: Foo = bar.foo; // 与上一句等价 } }

点运算符可以链式访问嵌套字段:

module 0x2::m { fun example() { let baz = Baz { foo: Foo { x: 3, y: true } }; let x = baz.foo.x; // x = 3; } }

点语法还可以用来修改字段:

module 0x2::m { fun example() { let foo = Foo { x: 3, y: true }; foo.x = 42; // foo = Foo { x: 42, y: true } foo.y = !foo.y; // foo = Foo { x: 42, y: false } let bar = Bar { foo }; // bar = Bar { foo: Foo { x: 42, y: false } } bar.foo.x = 52; // bar = Bar { foo: Foo { x: 52, y: false } } bar.foo = Foo { x: 62, y: true }; // bar = Bar { foo: Foo { x: 62, y: true } } } }

点语法同样适用于对结构体的引用:

module 0x2::m { fun example() { let foo = Foo { x: 3, y: true }; let foo_ref = &mut foo; foo_ref.x = foo_ref.x + 1; } }

1.5 特权结构体操作(Privileged Struct Operations)

对结构体类型T的大多数操作只能在声明T的模块内部执行

  • 结构体类型只能在其定义模块内被创建(打包)与销毁(解包);
  • 结构体的字段只能在其定义模块内被访问。

遵循这些规则,如果你想在模块外部修改自己的结构体,就需要为其提供公共 API(本章末尾的示例会展示这一点)。

然而,结构体的_类型_始终对其他模块或脚本可见:

// m.move module 0x2::m { struct Foo has drop { x: u64 } public fun new_foo(): Foo { Foo { x: 42 } } }
// n.move module 0x2::n { use 0x2::m; struct Wrapper has drop { foo: m::Foo } fun f1(foo: m::Foo) { let x = foo.x; // ^ error! 这里无法访问 `foo` 的字段 } fun f2() { let foo_wrapper = Wrapper { foo: m::new_foo() }; } }

默认情况下,结构体没有可见性修饰符——所有结构体操作都限制在定义模块内。Move 2.4 引入了显式可见性修饰符,见下文结构体可见性一节。

1.6 所有权(Ownership):资源保护的本质

如前所述,结构体默认是线性且瞬态的,不能被复制或丢弃。这一特性在建模真实世界的资源(如货币)时非常有用——你显然不希望钱被复制或在流通中丢失:

module 0x2::m { struct Foo { x: u64 } public fun copying_resource() { let foo = Foo { x: 100 }; let foo_copy = copy foo; // error! 'copy'-ing 需要 'copy' 能力 let foo_ref = &foo; let another_copy = *foo_ref // error! 解引用需要 'copy' 能力 } public fun destroying_resource1() { let foo = Foo { x: 100 }; // error! 函数返回时 foo 仍持有值, // 这种销毁需要 'drop' 能力 } public fun destroying_resource2(f: &mut Foo) { *f = Foo { x: 100 } // error! // 通过写入销毁旧值需要 'drop' 能力 } }

修复第二个示例(fun destroying_resource1)需要手动"解包"资源:

module 0x2::m { struct Foo { x: u64 } public fun destroying_resource1_fixed() { let foo = Foo { x: 100 }; let Foo { x: _ } = foo; } }

回忆一下:你只能在资源定义所在的模块内解构资源。这一限制可以被用来在系统中强制某些不变量,例如货币守恒(conservation of money)。

另一方面,如果你的结构体并不代表什么宝贵的东西,可以通过添加copydrop能力,得到一个在其他编程语言中更常见、更熟悉的结构体值:

module 0x2::m { struct Foo has copy, drop { x: u64 } public fun run() { let foo = Foo { x: 100 }; let foo_copy = copy foo; // ^ 这行复制 foo,而 `let x = foo` 或 `let x = move foo` 都是移动 foo let x = foo.x; // x = 100 let x_copy = foo_copy.x; // x = 100 // foo 与 foo_copy 在函数返回时都被隐式丢弃 } }

1.7 将资源存储到全局存储

具有key能力的结构体可以直接保存到持久化全局存储中。所有存储在那些key结构体内部的值都必须具有store能力。更详细的说明见能力与全局存储章节。

1.8 实战示例:Coin 与几何类型

示例 1:Coin(代币)
module 0x2::m { // 我们不希望 Coin 被复制,因为那会复制这份"钱", // 所以不给结构体 'copy' 能力。 // 同理,我们不希望程序员销毁 Coin,所以不给 'drop' 能力。 // 但我们*希望*模块用户可以把这个 coin 存入持久化全局存储, // 所以授予 'store' 能力。这个结构体只会出现在全局存储中 // 的其他资源内部,所以不授予 'key' 能力。 struct Coin has store { value: u64, } public fun mint(value: u64): Coin { // 你应该用某种访问控制来保护此函数,防止任何人无限铸造。 Coin { value } } public fun withdraw(coin: &mut Coin, amount: u64): Coin { assert!(coin.value >= amount, 1000); coin.value = coin.value - amount; Coin { value: amount } } public fun deposit(coin: &mut Coin, other: Coin) { let Coin { value } = other; coin.value = coin.value + value; } public fun split(coin: Coin, amount: u64): (Coin, Coin) { let other = withdraw(&mut coin, amount); (coin, other) } public fun merge(coin1: Coin, coin2: Coin): Coin { deposit(&mut coin1, coin2); coin1 } public fun destroy_zero(coin: Coin) { let Coin { value } = coin; assert!(value == 0, 1001); } }

仓库印证:Aptos 框架中的真实实现正是采用这一建模思路。在 coin.move 中可以看到:

  • struct Coin<phantom CoinType> has store:可转移、可存储但不可复制、不可丢弃的代币本身;
  • struct CoinStore<phantom CoinType> has key:作为全局存储根(账户持有的代币余额所在处);
  • struct CoinInfo<phantom CoinType> has key:存储代币的元信息(名称、符号、精度等)。 其中phantom类型参数只用于类型区分,不参与存储布局,这是泛型与能力结合的典型用法。同时,account.move 中的struct Account has key, store也印证了"key类型作为全局存储根"的设计模式。
示例 2:几何类型(Point 与 Circle)
module 0x2::point { struct Point has copy, drop, store { x: u64, y: u64, } public fun new(x: u64, y: u64): Point { Point { x, y } } public fun x(p: &Point): u64 { p.x } public fun y(p: &Point): u64 { p.y } fun abs_sub(a: u64, b: u64): u64 { if (a < b) { b - a } else { a - b } } public fun dist_squared(p1: &Point, p2: &Point): u64 { let dx = abs_sub(p1.x, p2.x); let dy = abs_sub(p1.y, p2.y); dx*dx + dy*dy } }
module 0x2::circle { use 0x2::point::{Self, Point}; struct Circle has copy, drop, store { center: Point, radius: u64, } public fun new(center: Point, radius: u64): Circle { Circle { center, radius } } public fun overlaps(c1: &Circle, c2: &Circle): bool { let dist_squared_value = point::dist_squared(&c1.center, &c2.center); let r1 = c1.radius; let r2 = c2.radius; dist_squared_value <= r1*r1 + 2*r1*r2 + r2*r2 } }

注意:Circle中嵌套了另一个模块定义的Point结构体作为字段类型——这印证了前面"结构体类型对外可见、字段操作仅限定义模块"的规则:circle模块可以持有Point值并通过point模块公开的 getter(xydist_squared)来操作它。


二、位置结构体(Positional Structs)

自语言版本 2.0 起

结构体可以声明为拥有_位置字段_(positional fields),即字段不是命名而是编号的。位置结构体的行为与普通结构体类似,只是提供了一种不同的语法,可能更适合字段较少的场景。

位置结构体的字段按照出现顺序赋值。下面的示例中,字段0的类型是u64,字段1的类型是u8

module 0x2::m { struct Pair(u64, u8); }

位置结构体的能力声明在字段列表之后,而非之前:

module 0x2::m { struct Pair(u64, u8) has copy, drop; }

对于纯类型标签(常用于 Move 代码中的 phantom 类型),参数列表可以完全省略:

module 0x2::m { struct TypeTag has copy, drop; }

位置结构体的值使用PositionalStructs(arguments)语法创建和解构:

module 0x2::m { fun work() { let value = Pair(1, true); let Pair(number, boolean) = value; assert!(number == 1 && boolean == true); } }

位置结构体的字段可以使用位置作为字段选择器来访问。例如在上述代码中,value.0value.1可以在不解构value的情况下访问两个字段。


三、部分模式(Partial Patterns)

自语言版本 2.0 起

模式可以使用..记法来匹配命名字段结构体或变体中剩余的、未列出的字段,也可以匹配位置字段结构体或变体开头或结尾的省略字段。示例如下:

module 0x2::m { struct Foo{ x: u8, y: u16, z: u32 } struct Bar(u8, u16, u32); fun foo_get_x(self: &Foo): u16 { let Foo{y, ..} = self; x } fun bar_get_0(self: &Foo): u8 { let Bar(x, ..) = self; x } fun bar_get_2(self: &Foo): u52 { // 对于位置结构体,也可以把 .. 放在开头 let Bar(.., z) = self; z } }

注意:部分模式目前不能用作赋值的左侧。虽然可以使用let Bar(x, ..) = v,但目前还不支持let x; Bar(x, ..) = v


四、枚举(Enums)

自语言版本 2.0 起

枚举类型与结构体类型类似,但支持定义数据布局的多个_变体_(variants)。每个变体拥有自己独立的字段集合。枚举变体在表达式中被支持,并带有测试、匹配与解构它们的工具。

4.1 枚举类型声明

一个枚举类型声明列出了不同的变体:

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) }

4.2 构造枚举值

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

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

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

let color = Color::Blue;

4.3 枚举变体的名称解析

枚举的变体名称需要用枚举类型名限定,如VersionedData::V1

注意:目前use子句的别名尚不支持枚举变体,但会在后续语言版本中加入。

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

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

4.4 匹配枚举值(match 表达式)

枚举值的值可以通过 match 表达式检查。例如:

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

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

fun scale_radius(self: &mut Shape, factor: u64) { match (self) { Circle{radius: r} => *r = *r * factor, _ => {} // 如果不是 Circle 则什么都不做 } }

match 表达式中提供的模式按文本出现顺序顺序求值,直到找到匹配。如果所有已知模式没有被全部覆盖,则属于编译期错误。

模式可以嵌套并包含条件(guard):

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);

注意在上面的例子中,最后一个 match 子句(_)覆盖了Ok(Err(_))Err(_)两种模式。虽然在运行时,前面的子句对c的所有值都匹配Ok(Err(c)),但由于条件的存在,编译器无法确定所有情况都被覆盖:match 表达式中的条件在追踪覆盖时不作考虑。因此上面 match 表达式中的前两个子句不足以满足匹配完整性,需要额外的子句来避免编译错误。

4.5 Match 表达式扩展

自语言版本 2.4 起

除了通用match扩展(原始类型判别器、范围模式、通过引用匹配原始值,见 Match 表达式),Move 2.4 还增加了专门适用于结构体与枚举模式的进一步扩展:

  • 嵌套在结构体和枚举变体模式中的字面量与范围模式;
  • 通过&&mut引用匹配结构体和枚举值,包括嵌套字面量;
  • 混合元组匹配:某些位置是原始类型而其他位置不是的元组匹配。
嵌套在结构体与枚举模式中的字面量与范围

原始字面量和范围模式可以嵌套出现在结构体模式与枚举变体模式内部,与变量绑定和_通配符自由混用。这对命名字段变体和位置变体都成立:

enum Inner has drop { A(u64), B } enum Outer has drop { W(Inner), X } fun deep(o: Outer): u64 { match (o) { Outer::W(Inner::A(1)) => 100, Outer::W(Inner::A(_)) => 200, Outer::W(Inner::B) => 300, Outer::X => 400, } } struct S has drop { x: u64, y: u64 } fun split(s: S): u64 { match (s) { S { x: 1, y } => y + 100, S { x: _, y: _ } => 0, } } enum Pair has drop { P(u64, u64), Q } fun pair_match(p: Pair): u64 { match (p) { Pair::P(1, 2) => 10, Pair::P(x, 2) => x + 100, Pair::P(_, _) => 20, Pair::Q => 30, } }

范围模式在相同的位置上工作:

enum E has drop { V1(u64), V2 } fun bucket(e: E): u64 { match (e) { E::V1(0..100) => 1, E::V1(100..=999) => 2, E::V1(_) => 3, E::V2 => 4, } }
通过引用匹配结构体与枚举

match表达式可以以结构体或枚举值的不可变或可变引用作为判别器。变体或字段模式可以包含嵌套字面量与范围。此类模式中的变量绑定捕获的是内部字段的引用,因此 arm 体中需要按通常方式解引用:

enum Pair has drop { P(u64, u64), Q } fun ref_match(p: &Pair): u64 { match (p) { Pair::P(1, 2) => 10, Pair::P(x, 2) => *x + 100, // x: &u64 Pair::P(_, _) => 20, Pair::Q => 30, } }

&mut判别器以相同方式被支持。

混合元组判别器(Mixed-Tuple Discriminators)

判别器现在可以是某些位置为原始类型、某些位置不是的元组,即_混合元组匹配_:

enum Data has drop { V1(u8), V2(u8) } fun make_pair(x: u8): (Data, u8) { (Data::V1(x), x) } fun classify(x: u8): u8 { match (make_pair(x)) { (Data::V1(a), 1) => a + 10, (Data::V2(a), 2) => a + 20, (Data::V1(a), y) if y > 3 => a + y, _ => 99, } }

4.6 测试枚举变体(is 运算符)

借助is运算符,可以检查给定的枚举值是否属于某个变体:

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

该运算符允许指定由|分隔的变体列表。如果被测试表达式的类型已知,变体可以不必用枚举名限定:

assert!(data is V1|V2);

4.7 从枚举值中选取字段

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

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);

注意:如果枚举值没有带给定字段的变体,字段选取会中止(abort)。例如data1.age就是这种情况。此中止使用的 abort code 是0xCA26CBD9BE0B0001。按照std::error约定,该 code 属于std::error::INTERNAL类别,reason 为1

字段选取仅当该字段在所有变体中具有唯一名称和唯一类型时才可能。因此,下面的代码会产生编译期错误:

enum VersionedData has key { V1{name: String}, V2{name: u64}, } data.name // ^^^^^ 编译期错误:`name` 字段选取有歧义

4.8 在 let 中使用枚举模式

枚举变体模式可以在let语句中使用:

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

解包枚举值时如果变体不是预期的那个,会中止。为确保枚举的所有变体都被处理,推荐使用match表达式而非letmatch在编译期检查,确保所有变体都被覆盖。在某些情况下,像 Move Prover 这样的工具可以被用来验证let不会发生意外中止。

4.9 通过模式匹配销毁枚举

与结构体值类似,枚举值可以通过显式解包来销毁。枚举可以通过以下方式解包: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(); }

4.10 枚举的类型升级兼容性

一个枚举类型可以被另一个枚举类型升级,条件是新类型只在变体列表末尾新增变体。旧枚举类型中的所有变体必须出现在新类型中,且保持相同顺序、从开头开始。考虑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}, }

仓库印证:枚举类型在 Aptos 框架中已被广泛使用。例如 market_types.move 中用enum OrderStatus has drop, copy, store表示订单状态、用enum OrderCancellationReason表示取消原因,order_book.move 中甚至直接以enum OrderBook<M: store + copy + drop> has store作为全局存储根。这些真实代码印证了枚举在状态机建模、事件描述与数据版本化中的典型用途。


五、结构体可见性(Struct Visibility)

自语言版本 2.4 起

默认情况下,结构体的构造、销毁与字段访问都是模块私有的,如特权结构体操作所述。Move 2.4 引入了显式可见性修饰符,允许外部模块执行这些操作。

5.1 语法

将修饰符放在struct关键字之前:

module 0x42::shapes { // 任何模块都可访问 public struct Point { x: u64, y: u64, } // 同一包内的模块可访问 package struct Config { value: u64, } } module 0x42::lib { friend 0x42::consumer; // 仅声明的 friend 模块可访问 friend struct Token { amount: u64, } }

5.2 跨模块访问

具有足够可见性的外部模块可以构造和销毁结构体,并读写其字段:

module 0x42::shapes { public struct Point { x: u64, y: u64, } } module 0x42::user { use 0x42::shapes::Point; fun mirror(p: Point): Point { let Point { x, y } = p; // 解构 Point { x: y, y: x } // 构造 } fun shift_x(p: &mut Point, delta: u64) { p.x = p.x + delta; // 读写字段 } }

六、枚举可见性(Enum Visibility)

自语言版本 2.4 起

默认情况下,枚举的构造、解构、匹配与字段选取都限制在定义模块内。Move 2.4 为枚举引入了显式可见性修饰符,遵循与结构体与枚举可见性相同的模型。

6.1 语法

将修饰符放在enum关键字之前。对于带能力的枚举,has子句照常跟在变体列表之后:

module 0x42::types { // 任何模块都可访问 public enum Color has copy, drop { Red, Green, Blue, } // 同一包内的模块可访问 package enum Status has drop { Active, Inactive, } } module 0x42::lib { friend 0x42::consumer; // 仅声明的 friend 模块可访问 friend enum Event has drop { Created, Updated, Deleted, } }

6.2 跨模块访问

外部模块具有足够可见性时,可以构造和销毁枚举、测试变体、从变体中选取字段并修改它们:

module 0x42::types { public enum Shape has drop { Circle { radius: u64 }, Rectangle { width: u64, height: u64 }, } } module 0x42::user { use 0x42::types::Shape; fun area(s: Shape): u64 { match (s) { Shape::Circle { radius } => radius * radius, Shape::Rectangle { width, height } => width * height, } } fun is_circle(s: &Shape): bool { s is Shape::Circle } }

七、结构体与枚举的可见性(Struct and Enum Visibility)

自语言版本 2.4 起

默认情况下,结构体和枚举的构造、销毁与字段访问都限制在定义模块内。Move 2.4 引入了允许外部模块执行这些操作的显式可见性修饰符。

7.1 可见性级别

有三个修饰符可用,与函数可见性关键字对应:

修饰符可访问范围
public任何模块
package同一包内的所有模块
friend在定义模块中被声明为friend的模块

public(package)public(friend)分别作为packagefriend的别名被接受,但不被鼓励,未来将被弃用。请优先使用简写形式。

7.2 性能考量

跨模块类型操作目前被编译为函数调用而非直接字节码指令。因此,它们还不是零成本抽象:比在定义模块内执行的等价操作更昂贵。这一状况有望在未来通过 VM 改进而改变。在此之前,只在确实需要跨模块访问时才使用可见性修饰符。

7.3 限制(Restrictions)

具有key能力的类型不能有可见性修饰符。作为全局存储根的类型必须保持模块私有,以维护全局存储的访问控制:

// ERROR: 具有 key 能力的类型不能是 public、package 或 friend public struct Resource has key { value: u64 } public enum VersionedData has key { V1 { name: vector<u8> } }

全局存储操作仍然仅限模块内。即使对public类型,move_tomove_fromborrow_globalborrow_global_mut仍仅限于定义模块内使用。

7.4 交易参数(Transaction Arguments)

如果类型具有copy能力且没有key能力,public结构体和枚举可以作为 entry 和 view 函数的参数传递。所有字段类型本身也必须是有效的参数类型(递归地):

module 0x42::types { public struct Point has copy, drop { x: u64, y: u64, } public enum Direction has copy, drop { North, South, East, West, } // Point 和 Direction 都可以直接作为交易参数传递 entry fun move_to_point(s: &signer, destination: Point) { .. } entry fun move_player(s: &signer, dir: Direction) { .. } }

7.5 可升级性(Upgradability)

在包升级中,结构体和枚举的可见性可以按以下方式变化。一般原则是:不允许任何会破坏定义包外部代码的转换public永远不能被收窄,但包内级别之间的转换是安全的,因为它们的消费者位于同一包内,并与升级一起原子地重新发布。

  • 私有类型可以升级为packagefriendpublic
  • friendpackage类型可以升级为public
  • friendpackage类型可以被收窄回私有。

八、设计决策速查表

综合全文,在设计链上类型时可以参考以下决策路径:

业务需求推荐能力组合理由
表示不可复制、不可丢失的资产(如 Coin)store(必要时加key防止复制与丢失,允许存入全局存储
普通数据容器,行为接近其他语言的类copy, drop(必要时加store可自由复制、丢弃
作为全局存储根key(内部字段需storemove_to/borrow_global
多形态数据(状态机、版本化数据)使用enum+ 按需能力变体清晰表达状态,match保证穷尽
需要外部模块构造/解构/访问字段(2.4+)public/package/friend修饰符提供跨模块能力的同时保留控制
需要作为交易参数传入(2.4+)public+copy、无keyentry/view 函数参数的要求

进一步阅读

  • 泛型与能力(generics-and-abilities):理解copydropstorekey四种能力的完整语义;
  • 全局存储(global-storage):move_tomove_fromborrow_global等操作;
  • 条件与循环(conditionals-and-loops):通用 match 扩展与范围模式的完整语法;
  • 实战参考:coin.move、account.move、market_types.move 等框架源码中的类型设计范例。

【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 8:45:50

iOS应用架构分层与MVVM模式实战解析

1. iOS应用架构分层详解1.1 用户界面层(UI Layer)的设计哲学现代iOS开发中&#xff0c;UI层已经发生了革命性的变化。从传统的UIKit命令式编程到SwiftUI的声明式范式&#xff0c;苹果生态系统的UI构建方式正在经历重大转型。在实际项目中&#xff0c;我通常会这样构建UI层&…

作者头像 李华
网站建设 2026/9/18 8:45:28

Python正则表达式核心实战:从匹配规则到踩坑避雷指南

正则表达式这东西&#xff0c;Python新手基本都会遇到。学的时候觉得"不就是匹配字符串吗"&#xff0c;等真的动手写才发现&#xff0c;要么匹配不到&#xff0c;要么匹配多了&#xff0c;那个*和看起来差不多&#xff0c;用起来结果完全不一样。我自己当年学的时候也…

作者头像 李华
网站建设 2026/9/18 8:45:13

论文降AI工具测评与学术写作优化指南

## 1. 论文降AI工具的核心需求解析去年帮导师审研究生论文时发现一个现象&#xff1a;超过60%的提交稿都带着明显的AI生成痕迹。从过度工整的段落结构到标志性的"综上所述"式结尾&#xff0c;这些特征就像指纹一样暴露在查重系统里。对于预算有限的学生群体&#xff…

作者头像 李华
网站建设 2026/9/18 8:44:26

钢铁ERP关键用户培训手册:从业务流程到SOP的实战编写指南

简介&#xff1a;这是一份某钢铁集团ERP关键用户培训使用手册&#xff0c;系达钢ERP项目中的正式交付文档&#xff0c;面向企业内ERP关键用户、财务及供应链岗位人员&#xff0c;帮助其掌握用友NC客户端的配置、登录、主界面操作、单据状态与基础数据设置等核心技能。资源共1个…

作者头像 李华
网站建设 2026/9/18 8:44:19

手算卷积入门:从6×6图像到CNN每一层的空间逻辑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华