Move/Sui 基础语法 - module/struct/function/acquires + Counter模块
### 1. Move 语言设计哲学
日期: 2026-05-22 方向: Move / Sui 阶段: 第三阶段:安全审计 标签: #move #sui #module #struct #object-model #counter
今日目标
- 理解 Move 语言的核心设计哲学——资源安全性
- 掌握 Move 的基本语法:module、struct、function、abilities
- 理解 Sui Move 与 Aptos Move 的关键差异
- 实现一个完整的 Counter 模块(Sui Move 版本)
核心概念
1. Move 语言设计哲学
Move 是专为区块链设计的编程语言,最初由 Facebook(Meta)Diem 项目创建。其核心设计原则:
| 原则 | 说明 | 与 Solidity 对比 |
|---|---|---|
| 资源安全 | 资源不能被复制或隐式丢弃 | Solidity 中 token 余额只是数字 |
| 类型线性 | 每个资源有明确的所有权和生命周期 | Solidity 无此概念 |
| 形式化验证 | 内置 Move Prover 支持 | Solidity 依赖外部工具 |
| 模块封装 | struct 只能在定义模块内操作 | Solidity 合约间可直接操作 |
Move 的"资源"概念对应现实世界的资产——你不能凭空复制一张钞票,也不能让它消失。这种安全性在语言层面就得到保证。
2. Module 声明
Module 是 Move 代码的基本组织单元,类似 Solidity 中的 contract:
// Sui Move module 声明
module counter::counter {
// module 内容
}
// 格式: module <package_address>::<module_name>
// 在 Sui 中,package_address 通常在 Move.toml 中配置
Aptos Move vs Sui Move 的 module 差异
// Aptos Move: 使用地址前缀
module 0x1::coin {
// ...
}
// Sui Move: 使用包名前缀
module my_package::coin {
// ...
}
3. Struct 与 Abilities
Struct 是 Move 中定义数据类型的方式。每个 struct 可以拥有不同的 abilities(能力),控制其行为:
四种 Abilities
| Ability | 含义 | 说明 |
|---|---|---|
copy | 可以被复制 | 值类型行为,如整数 |
drop | 可以被隐式丢弃 | 离开作用域时自动销毁 |
store | 可以存储在其他 struct 中 | 可作为字段被包含 |
key | 可以作为存储中的顶层对象 | Sui 中表示是一个 Object |
Abilities 组合含义
// 纯数据类型:可复制、可丢弃、可存储
struct Point has copy, drop, store {
x: u64,
y: u64,
}
// 资源类型(Sui Object):有key + store,不能copy/drop
// 这意味着它不能被复制,不能被随意丢弃——必须显式处理
struct Counter has key, store {
id: UID, // Sui 对象必须有 id 字段
value: u64,
}
// 一次性使用类型:只有 drop
// 常用于 witness pattern
struct COUNTER has drop {}
关键规则
没有 copy → 不能复制(资产安全!)
没有 drop → 不能丢弃(必须显式消费或转移)
没有 store → 不能存入其他结构体
没有 key → 不能作为独立对象存在于存储中
4. Function 类型
Move 中有多种函数可见性修饰符:
module example::demo {
// 私有函数:仅模块内部可调用
fun internal_helper(): u64 {
42
}
// 公共函数:任何模块都可以调用
public fun get_value(): u64 {
internal_helper()
}
// entry 函数:可以作为交易的入口点(从客户端直接调用)
// entry 函数不能有返回值(在 Sui Move 中可以有返回值但有限制)
entry fun do_something() {
// ...
}
// public entry:既可以被其他模块调用,也可以作为交易入口
public entry fun public_action() {
// ...
}
// friend 函数(Sui Move):仅允许指定的 friend 模块调用
public(friend) fun friend_only(): u64 {
100
}
}
| 可见性 | 模块内部 | 其他模块 | 交易入口 |
|---|---|---|---|
fun (private) | Yes | No | No |
public fun | Yes | Yes | No |
entry fun | Yes | No | Yes |
public entry fun | Yes | Yes | Yes |
public(friend) fun | Yes | Friend 模块 | No |
5. Aptos Move 的全局存储操作
在 Aptos Move 中,资源存储在账户地址下,使用以下全局操作:
// Aptos Move 风格(非 Sui)
module 0x1::counter {
struct Counter has key {
value: u64,
}
// move_to: 将资源存储到账户下
public fun create(account: &signer) {
move_to(account, Counter { value: 0 });
}
// borrow_global: 不可变借用某地址下的资源
public fun get_value(addr: address): u64 acquires Counter {
let counter = borrow_global<Counter>(addr);
counter.value
}
// borrow_global_mut: 可变借用
public fun increment(addr: address) acquires Counter {
let counter = borrow_global_mut<Counter>(addr);
counter.value = counter.value + 1;
}
// move_from: 从账户下移走资源(获取所有权)
public fun destroy(account: &signer): u64 acquires Counter {
let Counter { value } = move_from<Counter>(signer::address_of(account));
value
}
// exists: 检查资源是否存在
public fun has_counter(addr: address): bool {
exists<Counter>(addr)
}
}
acquires 关键字:当函数访问全局存储中的资源时,必须声明 acquires。这是 Aptos Move 特有的。
6. Sui Move 的对象模型(与 Aptos 的核心差异)
Sui Move 不使用全局存储操作 (move_to, borrow_global 等),而是使用对象模型:
Aptos Move Sui Move
───────── ────────
move_to(signer, resource) → transfer::transfer(object, recipient)
borrow_global<T>(addr) → 函数参数直接接收 &T 或 &mut T
borrow_global_mut<T>(addr) → 函数参数直接接收 &mut T
move_from<T>(addr) → 函数参数接收 T(按值传递=获取所有权)
exists<T>(addr) → 由 Sui runtime 在交易执行前验证
在 Sui 中,对象通过 UID 唯一标识,runtime 负责对象的存取:
// Sui Move: 对象作为函数参数传入
public fun increment(counter: &mut Counter) {
counter.value = counter.value + 1;
}
// 调用时,Sui runtime 根据 object ID 自动加载对象
// 开发者不需要手动从存储中取出
代码实战
Sui Move Counter 模块完整实现
/// 一个简单的计数器模块,展示 Sui Move 基础概念
module counter::counter {
// ===== 导入 =====
use sui::object::{Self, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
use sui::event;
// ===== 错误码 =====
const E_NOT_ZERO: u64 = 0;
const E_OVERFLOW: u64 = 1;
// ===== 事件 =====
struct CounterCreated has copy, drop {
id: address,
creator: address,
}
struct CounterIncremented has copy, drop {
id: address,
new_value: u64,
}
// ===== 对象定义 =====
/// Counter 对象 - 一个简单的计数器
/// has key: 可以作为 Sui 对象存在
/// has store: 可以被嵌套在其他对象中,也可以被转移
struct Counter has key, store {
id: UID,
value: u64,
owner: address, // 记录创建者
}
// ===== 公共函数 =====
/// 创建一个新的 Counter 对象并转移给调用者
public entry fun create(ctx: &mut TxContext) {
let sender = tx_context::sender(ctx);
let id = object::new(ctx);
let counter_addr = object::uid_to_address(&id);
let counter = Counter {
id,
value: 0,
owner: sender,
};
// 发送创建事件
event::emit(CounterCreated {
id: counter_addr,
creator: sender,
});
// 将对象转移给创建者(成为 owned object)
transfer::transfer(counter, sender);
}
/// 创建一个共享的 Counter(任何人都可以操作)
public entry fun create_shared(ctx: &mut TxContext) {
let sender = tx_context::sender(ctx);
let counter = Counter {
id: object::new(ctx),
value: 0,
owner: sender,
};
// 共享对象 - 任何人都可以访问和修改
transfer::share_object(counter);
}
/// 递增计数器
/// 参数类型 &mut Counter 表示需要可变引用(写权限)
public entry fun increment(counter: &mut Counter) {
assert!(counter.value < 18446744073709551615, E_OVERFLOW);
counter.value = counter.value + 1;
event::emit(CounterIncremented {
id: object::uid_to_address(&counter.id),
new_value: counter.value,
});
}
/// 按指定值递增
public entry fun increment_by(counter: &mut Counter, amount: u64) {
assert!(counter.value + amount >= counter.value, E_OVERFLOW); // 溢出检查
counter.value = counter.value + amount;
}
/// 重置计数器
public entry fun reset(counter: &mut Counter) {
counter.value = 0;
}
/// 获取当前值(只读)
/// 参数类型 &Counter 表示不可变引用(只读)
public fun value(counter: &Counter): u64 {
counter.value
}
/// 获取 owner
public fun owner(counter: &Counter): address {
counter.owner
}
/// 销毁计数器(只有值为0时才允许)
public entry fun destroy_zero(counter: Counter) {
assert!(counter.value == 0, E_NOT_ZERO);
let Counter { id, value: _, owner: _ } = counter;
object::delete(id);
}
/// 转移 Counter 给另一个地址
public entry fun transfer_counter(counter: Counter, recipient: address) {
transfer::transfer(counter, recipient);
}
// ===== 测试 =====
#[test_only]
use sui::test_scenario;
#[test]
fun test_create_and_increment() {
let owner = @0xA;
// 第一个交易:创建 counter
let mut scenario = test_scenario::begin(owner);
{
create(test_scenario::ctx(&mut scenario));
};
// 第二个交易:递增 counter
test_scenario::next_tx(&mut scenario, owner);
{
let mut counter = test_scenario::take_from_sender<Counter>(&scenario);
assert!(value(&counter) == 0, 0);
increment(&mut counter);
assert!(value(&counter) == 1, 1);
increment(&mut counter);
assert!(value(&counter) == 2, 2);
test_scenario::return_to_sender(&scenario, counter);
};
// 第三个交易:验证状态持久化
test_scenario::next_tx(&mut scenario, owner);
{
let counter = test_scenario::take_from_sender<Counter>(&scenario);
assert!(value(&counter) == 2, 3);
test_scenario::return_to_sender(&scenario, counter);
};
test_scenario::end(scenario);
}
#[test]
fun test_destroy_zero() {
let owner = @0xA;
let mut scenario = test_scenario::begin(owner);
{
create(test_scenario::ctx(&mut scenario));
};
test_scenario::next_tx(&mut scenario, owner);
{
let counter = test_scenario::take_from_sender<Counter>(&scenario);
assert!(value(&counter) == 0, 0);
destroy_zero(counter); // 值为0,允许销毁
};
test_scenario::end(scenario);
}
#[test]
#[expected_failure(abort_code = E_NOT_ZERO)]
fun test_destroy_non_zero_fails() {
let owner = @0xA;
let mut scenario = test_scenario::begin(owner);
{
create(test_scenario::ctx(&mut scenario));
};
test_scenario::next_tx(&mut scenario, owner);
{
let mut counter = test_scenario::take_from_sender<Counter>(&scenario);
increment(&mut counter);
destroy_zero(counter); // 值不为0,应该失败
};
test_scenario::end(scenario);
}
}
与 Solidity 的对比
// Solidity 版本的 Counter
contract Counter {
uint256 public value;
address public owner;
event CounterIncremented(uint256 newValue);
constructor() {
owner = msg.sender;
}
function increment() external {
value++;
emit CounterIncremented(value);
}
function reset() external {
require(msg.sender == owner, "Not owner");
value = 0;
}
}
对比表:
| 特性 | Solidity Counter | Sui Move Counter |
|---|---|---|
| 状态存储 | 合约内全局变量 | 独立对象(可多个实例) |
| 所有权 | 无内建所有权概念 | 对象有明确 owner |
| 创建多实例 | 需要 mapping 或工厂模式 | 每次调用 create 产生新对象 |
| 并发访问 | 整个合约串行 | 不同对象可并行处理 |
| 销毁 | 需自己实现逻辑 | 解构+delete,编译器保证完整性 |
关键要点总结
Move 语言核心特性
- 资源安全:struct 没有
copyability 就不能复制,没有drop就不能丢弃 - 模块封装:struct 的字段只能在定义模块内访问
- 显式所有权:Sui 中对象有明确的所有者,runtime 强制执行
- 静态类型:所有类型在编译时确定,不存在动态调用
Sui Move vs Aptos Move 核心差异
| 维度 | Aptos Move | Sui Move |
|---|---|---|
| 存储模型 | 全局存储(账户→资源) | 对象模型(对象有独立 ID) |
| 存取方式 | move_to/borrow_global | 对象作为函数参数传入 |
| 并行性 | 乐观并行(BlockSTM) | 对象级并行(owned object 无冲突) |
acquires | 需要声明 | 不需要 |
| 对象类型 | 无 | owned/shared/immutable |
abilities 速查表
key → Sui 对象(独立存在)
key + store → 可转移的 Sui 对象(最常见)
copy + drop → 纯值类型(如坐标点)
copy + drop + store → 可存储的值类型
drop → 一次性使用(witness pattern)
(无 ability) → 热土豆模式(必须在同一交易内消费)
常见误区
误区 1:"Move 的 struct 类似 Solidity 的 struct"
错误!Solidity 的 struct 是纯数据结构,可以随意复制。Move 的 struct 通过 abilities 系统控制行为——没有 copy 就不能复制,没有 drop 就不能丢弃。这是安全性的根基。
误区 2:"Sui 的对象就是 Solidity 的合约实例"
不完全对。Solidity 中每个合约部署一个实例,状态共享。Sui 中同一个模块可以创建无数个独立对象,每个有独立 owner 和独立 ID,可以并行操作。
误区 3:"entry 函数 = public 函数"
错误!entry 函数可以从客户端直接调用(作为交易入口),但不能被其他 Move 模块调用。public 函数可以被其他模块调用,但不能直接作为交易入口。public entry 二者兼备。
误区 4:"Move 没有合约升级"
Sui Move 支持包升级,但有严格限制——不能删除已有的 public 函数,不能修改已有 struct 的布局。这比 Solidity 的 proxy pattern 更安全但也更受限。
面试关联
Q: Move 的资源安全性如何防止 Token 被复制或凭空产生?
简短回答:Move 的 abilities 系统在编译期保证——没有 copy ability 的 struct 不能被复制,没有 drop ability 的 struct 不能被丢弃。Token 类型通常只有 key + store,任何复制或丢弃的尝试都会导致编译错误。
详细回答:
- 在 Solidity 中,Token 余额只是
mapping(address => uint256)中的数字,合约 bug 可能导致凭空增减 - 在 Move 中,Token 是一个 struct 对象,类型系统保证它只能被创建(mint)、转移(transfer)、合并(join)、拆分(split)或销毁(burn)
- 编译器层面的保证比运行时检查更可靠——根本无法编写出复制 Token 的代码
Q: Sui 的对象模型比 EVM 的存储模型有什么优势?
回答:
- 并行性:不同 owned object 可以在不同的验证者上并行处理,EVM 中整个合约状态串行访问
- 明确所有权:对象有明确 owner,无需在合约中维护 access control
- 组合性:对象可以嵌套(wrapping),形成层次结构
- 本地交易:只涉及 owned object 的交易不需要共识(Sui 的 fast path)
Q: 解释 Move 的 abilities 系统?
回答:Move 的四种 abilities(copy/drop/store/key)控制值的行为。copy 允许复制,drop 允许丢弃,store 允许存储在其他结构中,key 允许作为顶层存储对象。组合使用可实现不同安全级别——资产类型(key+store)不可复制不可丢弃,值类型(copy+drop+store)行为像普通数据,witness类型(仅drop)用于一次性能力证明。
参考资源
- Move Book (Sui) — Sui Move 官方教程
- Sui Move by Example — Sui 官方示例集
- Sui Developer Documentation — Sui 开发文档
- Aptos Move Tutorial — Aptos Move 教程(了解差异)
- Move Language Specification — Move 语言规范
- Move on Sui vs Aptos — 官方博客差异对比
- Awesome Move — Move 资源汇总