包(Package)

在 Move 语言中,包(Package)是代码组织的基本单位。每个包在发布到 Sui 区块链后,都会被分配一个唯一的链上地址作为标识。理解包的结构和工作机制,是编写和部署 Move 智能合约的第一步。

包的目录结构

使用 Sui CLI 创建一个新包:

sui move new my_package

生成的标准目录结构如下:

my_package/
├── Move.toml
├── sources/
│   └── my_module.move
└── tests/
    └── my_module_tests.move

各目录和文件的作用:

路径说明
Move.toml包的清单文件,声明包名、依赖、地址别名等
sources/存放所有 Move 源代码(.move 文件)
tests/存放测试代码
examples/可选目录,存放示例代码(不会被编译到主包中)

包与模块的关系

一个包可以包含多个模块(Module),每个模块又包含函数、类型(结构体)和常量。它们的层次关系可以这样理解:

package 0x...
    module a
        struct A1
        fun hello_world()
    module b
        struct B1
        fun hello_package()

在代码层面,每个 .move 文件通常定义一个模块:

// sources/cafe.move
module my_package::cafe;

public struct Coffee has drop {
    strength: u8,
}

public fun brew(strength: u8): Coffee {
    Coffee { strength }
}
// sources/bakery.move
module my_package::bakery;

public struct Bread has drop {
    flavor: vector<u8>,
}

public fun bake(flavor: vector<u8>): Bread {
    Bread { flavor }
}

上面的两个模块 cafebakery 同属于 my_package 这一个包。发布后,它们共享同一个链上地址。

包的发布与不可变性

使用以下命令将包发布到 Sui 网络:

sui client publish

发布后,包具有以下特性:

  • 唯一地址:每个已发布的包都有一个唯一的链上地址(如 0xabc123...),后续所有对该包中模块和函数的调用都通过此地址进行。
  • 不可变性:已发布的包是不可变对象(Immutable Object),任何人(包括发布者)都无法修改或删除它。这保证了链上合约代码的透明性和可审计性。

在代码中引用已发布的包

其他包可以通过地址引用已发布的模块:

module other_package::user;

use 0xabc123::cafe;

public fun drink() {
    let _coffee = cafe::brew(10);
}

首次发布与模块初始化器(init)

上一节讲的是发布之后包作为不可变对象存在;本节补充与同一次发布交易相关的、Move 模块里的一种特殊入口:模块初始化器 init

当你执行 sui client publish 并成功发布某个包时,Sui 运行时会按平台规则处理包内各模块的装载;若某模块提供了符合约束的 init 函数,它会在该次首次发布的上下文中被自动调用,用于一次性完成「冷启动」逻辑(例如:把管理员能力 Cap 转给发布者、创建并 share_object 某个注册表、或与一次性见证 OTW 配合向 package::claim 注册元数据等)。包升级不会再次执行 init——升级走的是 UpgradeCap 与兼容策略那一套(见下一节)。

init 在语言层面的要点(只记结论即可,写法与模式详见 第十二章 §12.3 · 模块初始化器 init):

要点说明
函数名必须为 init
可见性private(不显式写 public / public(package)
返回值
参数一种ctx: &mut TxContext或两种:一次性见证类型 OTW + ctx(OTW 名称须与模块名对应,见第十二章与第十一章 · OTW
调用次数模块随该包首次成功发布时执行;升级不重复

你在第三章 · Hello Sui里发布的 todo_list 示例没有init——这是合法的;许多最小示例只靠 entry/public 函数即可。一旦需要在「第一次上链」时自动布置对象或权限,再在对应模块里添加 init 即可。

包的升级与 UpgradeCap

虽然已发布的包不可变,但 Sui 提供了包升级机制,允许开发者发布一个新版本的包来替代旧版。

UpgradeCap

当一个包首次发布时,发布者会收到一个 UpgradeCap 对象。持有此对象即拥有升级该包的权限:

sui client upgrade

升级时需注意以下兼容性规则:

升级策略允许的变更
compatible可以添加新函数和新模块,不能删除或修改已有的公共函数签名
additive只能添加新模块,不能修改现有模块
dep_only只能更改依赖项

放弃升级权限

如果希望让包彻底不可变(无法再升级),可以销毁 UpgradeCap

module my_package::config;

use sui::package;

public fun make_immutable(cap: package::UpgradeCap) {
    package::make_immutable(cap);
}

调用此函数后,包将永远无法再被升级。

包的命名规范

  • 包名使用 snake_case,如 my_defi_app
  • 模块名同样使用 snake_case,如 liquidity_pool
  • 包名在 Move.toml[package] 段中声明,同时作为 [addresses] 中的命名地址使用
[package]
name = "my_defi_app"

[addresses]
my_defi_app = "0x0"

小结

包是 Move 项目的顶层组织单位。一个包由 Move.toml 清单文件和 sources/ 目录中的模块组成。发布到链上后,包获得唯一地址并变为不可变。通过 UpgradeCap 机制,开发者可以在保持兼容性的前提下发布新版本。理解包的结构和生命周期,是构建 Sui 应用的基础。