1.1 Move 语言简介
什么是 Aptos Move?
Aptos Move 是一种专门为区块链设计的编程语言,由 Facebook(现 Meta)开发,最初为 Diem 项目设计。Aptos Move 语言的核心设计理念是资源安全和类型安全。
现由 Aptos 区块链使用,并扩展语法并重写编译器和升级虚拟机
Move 语言的特点
🔒 资源安全
- 资源模型:Move 将数字资产视为"资源",确保资产不能被复制或意外销毁
- 线性类型系统:每个资源只能被移动,不能被复制
- 所有权模型:明确的资源所有权和转移机制
🛡️ 类型安全
- 静态类型检查:编译时进行类型检查,避免运行时错误
- 内存安全:没有空指针、悬空引用等内存安全问题
- 形式化验证:支持数学证明程序正确性
📦 模块化设计
- Module 系统:代码组织在模块中,便于管理和重用
- 能力系统:通过能力(Abilities)控制类型的行为
- 泛型支持:支持泛型编程,提高代码复用性
Move 与其他语言的对比
特性 | Move | Solidity | Rust |
---|---|---|---|
资源安全 | ✅ 原生支持 | ❌ 需要手动管理 | ✅ 所有权系统 |
类型安全 | ✅ 静态类型 | ⚠️ 动态类型 | ✅ 静态类型 |
区块链专用 | ✅ 专为区块链设计 | ✅ 以太坊专用 | ❌ 通用语言 |
学习曲线 | 🟡 中等 | 🟢 简单 | 🔴 陡峭 |
Move 的应用场景
🪙 代币合约
// 简单的代币合约示例
module my_addr::basic_coin {
struct Coin has key {
value: u64,
}
public fun mint(account: &signer, value: u64) {
move_to(account, Coin { value })
}
}
🏛️ DeFi 协议
- 去中心化交易所(DEX)
- 借贷协议
- 流动性挖矿
- 衍生品合约
Move 生态系统
🛠️ 开发工具
- Aptos CLI:命令行工具
- Move Analyzer:vscode 插件
- Move Prover:形式化验证工具
- IDE 插件:VS Code 等编辑器支持
为什么选择 Move?
✅ 优势
- 安全性:资源模型天然防止资产丢失
- 可验证性:支持形式化验证
- 可升级性:支持模块升级
⚠️ 挑战
- 学习曲线:资源模型概念较新
- 工具链:相比 Solidity 工具链不够成熟
学习路径建议
🎯 初学者路径
- 基础概念:理解资源、模块、能力
- 语法学习:掌握基本语法和数据类型
- 简单项目:编写简单的代币合约
- 进阶特性:学习泛型、错误处理
- 实战项目:构建完整的 DeFi 协议
📚 推荐资源
- Aptos Move Book:Aptos Move 文档
- Aptos Move Tutorial:Aptos Move 官方教程
- Aptos Move Examples:官方示例
小结
Move 语言通过其独特的资源模型和类型系统,为区块链开发提供了更高的安全性和可靠性。虽然学习曲线相对较陡,但掌握 Move 将让你在区块链开发领域具有独特优势。
在接下来的章节中,我们将从最基础的 module 开始,逐步深入学习 Move 语言的各个方面。
下一节:1.2 开发环境搭建
1.2 开发环境搭建
环境要求
在开始学习 Move 之前,我们需要搭建一个完整的开发环境。
我们需要使用 Aptos cli 作为编译器和执行环境
🛠️ 必需工具
- Git:版本控制
- Rust:Move 工具链依赖
安装步骤
1. 安装 Aptos CLI
本文列出目前几种安装方式,更多方式详情可以查看文档:
https://aptos.dev/build/cli
Mac
- 使用 Homebrew 安装
brew install aptos
- 使用 Shell 脚本安装
curl -fsSL "https://aptos.dev/scripts/install_cli.sh" | sh
Linux
- 使用 Shell 脚本安装
curl -fsSL "https://aptos.dev/scripts/install_cli.sh" | sh
- ArchLinux 用户可以使用 AUR 安装
yay -S aptos-cli
Windows
- winget 安装
winget install aptos
- 使用 Chocolatey 安装
choco install aptos
- 使用 scoop 安装
scoop install https://aptos.dev/scoop/aptos.json
- 使用 PowerShell 脚本安装
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser; iwr https://aptos.dev/scripts/install_cli.ps1 | iex
验证安装
aptos --version
显示类似以下内容,表示安装成功(版本 7.6.0):
aptos 7.6.0
2. 安装 IDE 和插件
VS Code(推荐)
- 下载并安装 VS Code
- 安装以下插件:
- Move on Aptos:Aptos Move 支持,语法高亮和代码补全
- https://marketplace.visualstudio.com/items?itemName=AptosLabs.move-on-aptos
- Move on Aptos:Aptos Move 支持,语法高亮和代码补全
其他 IDE 选项
- IntelliJ IDEA:Move on Aptos
- https://plugins.jetbrains.com/plugin/14721-move-on-aptos
下一步
环境搭建完成后,我们就可以开始编写第一个 Aptos Move 程序了!
📋 检查清单
- Aptos CLI 安装成功
- IDE 配置完成
1.3 第一个 Aptos Move 程序
创建 Hello World 程序
让我们从最经典的 "Hello World" 程序开始,了解 Aptos Move 的基本语法结构。
项目结构
首先创建一个新的 Aptos Move 项目:
# 创建项目
mkdir 01-hello_blockchain
cd 01-hello_blockchain
aptos move init --name hello-blockchain --template hello-blockchain
这将创建一个名为 01-hello_blockchain
的目录,包含以下结构:
├── Move.toml
├── .gitignore
├── sources
│ └── hello_blockchain.move
└── tests
### 查看第一个模块
在 `sources/` 目录下打开 `hello_blockchain.move` 文件:
```move
module hello_blockchain::message {
use std::error;
use std::signer;
use std::string;
use aptos_framework::event;
#[test_only]
use std::debug;
//:!:>resource
struct MessageHolder has key {
message: string::String,
}
//<:!:resource
#[event]
struct MessageChange has drop, store {
account: address,
from_message: string::String,
to_message: string::String,
}
/// There is no message present
const ENO_MESSAGE: u64 = 0;
#[view]
public fun get_message(addr: address): string::String acquires MessageHolder {
assert!(exists<MessageHolder>(addr), error::not_found(ENO_MESSAGE));
borrow_global<MessageHolder>(addr).message
}
public entry fun set_message(account: signer, message: string::String)
acquires MessageHolder {
let account_addr = signer::address_of(&account);
if (!exists<MessageHolder>(account_addr)) {
move_to(&account, MessageHolder {
message,
})
} else {
let old_message_holder = borrow_global_mut<MessageHolder>(account_addr);
let from_message = old_message_holder.message;
event::emit(MessageChange {
account: account_addr,
from_message,
to_message: copy message,
});
old_message_holder.message = message;
}
}
#[test(account = @0x1)]
public entry fun sender_can_set_message(account: signer) acquires MessageHolder {
let msg: string::String = string::utf8(b"Running test for sender_can_set_message...");
debug::print(&msg);
let addr = signer::address_of(&account);
aptos_framework::account::create_account_for_test(addr);
set_message(account, string::utf8(b"Hello, Blockchain"));
assert!(
get_message(addr) == string::utf8(b"Hello, Blockchain"),
ENO_MESSAGE
);
}
}
代码解析
让我们逐行分析这个简单的 Move 程序:
1. 模块声明
module hello_blockchain::message {
module
:关键字,声明这是一个模块hello_blockchain::message
:模块的完整名称hello_blockchain
:地址(在 Move.toml 中定义)message
:模块名称
2. 导入标准库
use std::error;
use std::signer;
use std::string;
use aptos_framework::event;
#[test_only]
use std::debug;
use
:导入关键字std::error
:标准库中的错误处理模块std::signer
:处理签名者的模块std::string
:字符串处理模块aptos_framework::event
:Aptos 框架中的事件模块#[test_only]
:仅在测试环境中使用的模块
3. 资源定义
struct MessageHolder has key {
message: string::String,
}
//<:!:resource
#[event]
struct MessageChange has drop, store {
account: address,
from_message: string::String,
to_message: string::String,
}
-
struct
:定义一个结构体 -
MessageHolder
:存储消息的资源 -
has key
:具有 Key 能力,用于全局存储 -
message: string::String
:消息内容 -
#[event]
:声明这是一个事件结构体 -
MessageChange
:事件结构体,记录消息变更 -
account: address
:事件触发的账户地址 -
from_message: string::String
:变更前的消息 -
to_message: string::String
:变更后的消息
4. 常量定义
const ENO_MESSAGE: u64 = 0;
const
:声明一个常量ENO_MESSAGE
:表示没有消息的错误代码
5. 函数定义
#[view]
public fun get_message(addr: address): string::String acquires MessageHolder
public entry fun set_message(account: signer, message: string::String)
acquires MessageHolder
-
public
:访问修饰符,表示函数可以被其他模块调用 -
fun
:关键字,声明这是一个函数 -
get_message
:函数名 -
(addr: address)
:参数列表 -
: string::String
:返回类型 -
acquires MessageHolder
:声明函数需要获取MessageHolder
资源 -
{}
:函数体 -
entry
: 表示这是一个入口函数,可以从链下调用 -
account: signer
:签名者账户 -
message: string::String
:要设置的消息内容 -
acquires MessageHolder
:同样声明需要获取MessageHolder
资源
6. 函数实现
assert!(exists<MessageHolder>(addr), error::not_found(ENO_MESSAGE));
borrow_global<MessageHolder>(addr).message
assert!
:断言函数,检查条件是否为真exists<MessageHolder>(addr)
:检查地址是否存在MessageHolder
资源error::not_found(ENO_MESSAGE)
:如果不存在,抛出错误borrow_global<MessageHolder>(addr).message
:获取并返回消息内容
if (!exists<MessageHolder>(account_addr)) {
move_to(&account, MessageHolder {
message,
})
} else {
let old_message_holder = borrow_global_mut<MessageHolder>(account_addr);
let from_message = old_message_holder.message;
event::emit(MessageChange {
account: account_addr,
from_message,
to_message: copy message,
});
old_message_holder.message = message;
}
if (!exists<MessageHolder>(account_addr))
:检查是否存在MessageHolder
move_to(&account, MessageHolder { message })
:如果不存在,创建新的MessageHolder
else
分支:如果存在,获取现有的MessageHolder
event::emit(MessageChange { ... })
:触发消息变更事件old_message_holder.message = message
:更新消息内容
7. 测试函数
#[test(account = @0x1)]
public entry fun sender_can_set_message(account: signer) acquires MessageHolder {
let msg: string::String = string::utf8(b"Running test for sender_can_set_message...");
debug::print(&msg);
let addr = signer::address_of(&account);
aptos_framework::account::create_account_for_test(addr);
set_message(account, string::utf8(b"Hello, Blockchain"));
assert!(
get_message(addr) == string::utf8(b"Hello, Blockchain"),
ENO_MESSAGE
);
}
#[test(account = @0x1)]
:声明这是一个测试函数,并指定测试账户地址public entry fun sender_can_set_message(account: signer)
:let msg: string::String = string::utf8(b"Running test for sender_can_set_message...");
:创建测试消息debug::print(&msg);
:打印调试信息let addr = signer::address_of(&account);
:获取签名者地址aptos_framework::account::create_account_for_test(addr);
:为测试创建账户set_message(account, string::utf8(b"Hello, Blockchain"));
:设置消息assert!(get_message(addr) == string::utf8(b"Hello, Blockchain"), ENO_MESSAGE);
:断言消息设置成功
编译和运行
1. 编译项目
aptos move build --named-addresses hello_blockchain=0x1234
2. 运行测试
aptos move test --named-addresses hello_blockchain=0x1234
Move 语法要点
1. 注释
// 单行注释
/// 文档注释
/* 多行注释 */
2. 变量声明
let x = 10; // 类型推断
let y: u64 = 20; // 显式类型声明
3. 条件语句
if (condition) {
// 代码块
} else {
// 代码块
}
4. 循环
while (condition) {
// 循环体
}
5. 函数调用
module_name::function_name(arg1, arg2);
6. 断言
assert!(condition, error_code);
常见错误和调试
调试技巧
- 使用
debug::print()
输出调试信息 - 在测试中使用
assert!()
验证结果
小结
通过这个简单的 Hello World 程序,我们学习了:
- 模块结构:如何声明和组织模块
- 函数定义:如何定义和调用函数
- 测试编写:如何为代码编写测试
- 调试技巧:如何使用调试输出
下一节:2.1 Module 概念与结构
2.1 Module 概念与结构
什么是 Module?
在 Move 中,Module(模块) 是代码组织的基本单位。模块类似于其他编程语言中的类或包,它封装了相关的函数、结构体和常量。
Module 的核心概念
1. 命名空间
module my_addr::math {
// 模块内容
}
my_addr
:地址(Address),类似于包名math
:模块名my_addr::math
:完整的模块标识符
2. 封装性
- 模块内的代码可以访问模块内的所有内容
- 其他模块只能访问被标记为
public
的内容 - 提供了良好的封装和抽象
3. 可重用性
- 模块可以被其他模块导入和使用
- 支持模块的组合和扩展
Module 的基本结构
完整的模块示例
module my_addr::bank {
use std::signer;
use std::debug;
// 错误码常量
const EINSUFFICIENT_BALANCE: u64 = 1;
const EINVALID_AMOUNT: u64 = 2;
// 结构体定义
struct Account has key {
balance: u64,
owner: address,
}
// 事件结构体
#[event]
struct DepositEvent has drop, store {
account: address,
amount: u64,
}
// 公共函数
public fun create_account(account: &signer) {
let account_addr = signer::address_of(account);
move_to(account, Account {
balance: 0,
owner: account_addr,
});
debug::print(&b"Account created for: ");
debug::print(&account_addr);
}
// 公共函数
public fun deposit(account: &signer, amount: u64) acquires Account {
assert!(amount > 0, EINVALID_AMOUNT);
let account_addr = signer::address_of(account);
let account_ref = &mut Account[account_addr];
account_ref.balance += amount;
debug::print(&b"Deposited: ");
debug::print(&amount);
}
// 公共函数
public fun withdraw(account: &signer, amount: u64): u64 acquires Account {
assert!(amount > 0, EINVALID_AMOUNT);
let account_addr = signer::address_of(account);
let account_ref = &mut Account[account_addr];
assert!(account_ref.balance >= amount, EINSUFFICIENT_BALANCE);
account_ref.balance -= amount;
debug::print(&b"Withdrawn: ");
debug::print(&amount);
amount
}
// 公共函数
public fun get_balance(account_addr: address): u64 acquires Account{
let account = &Account[account_addr];
account.balance
}
// 私有函数(只能在模块内部调用)
fun validate_amount(amount: u64): bool {
amount > 0
}
}
Module 的组成部分
1. 模块声明
module <address>::<module_name> {
// 模块内容
}
2. 导入语句
use std::signer; // 导入标准库模块
use my_addr::math; // 导入自定义模块
3. 常量定义
常量定义只能定义基础类型变量
const <name>: <type> = <value>;
const ERROR_CODE: u64 = 1;
const MAX_BALANCE: u64 = 1000000;
4. 结构体定义
struct MyStruct has key, store {
field1: u64,
field2: vector<u8>,
}
5. 函数定义
public fun public_function() {
// 公共函数
}
fun private_function() {
// 私有函数
}
Module 的访问控制
访问修饰符
1. public
public fun public_function() {
// 可以被其他模块调用
}
2. 默认(私有)
fun private_function() {
// 只能在模块内部调用
}
3. public(friend)
需要在模块声明中指定友元模块
public(friend) fun friend_function() {
// 只能被友元模块调用
}
4. public(package)
无需声明 Friend 模块,当前包内可以直接访问
public (package) fun package_function() {
// 只能被同一包内的模块调用
}
访问控制示例
module my_addr::access_control {
use std::debug;
// 友元模块
friend my_addr::other_module;
// 公共函数 - 任何模块都可以调用
public fun public_function() {
debug::print(&b"This is public");
}
// 私有函数 - 只能在模块内部调用
fun private_function() {
debug::print(&b"This is private");
}
// 公共函数调用私有函数
public fun call_private() {
private_function(); // 可以调用私有函数
}
// 友元函数 - 只能被指定模块调用
public(friend) fun friend_function() {
debug::print(&b"This is a friend function");
}
// 包内函数 - 只能被同一包内的模块调用
public(package) fun package_function() {
debug::print(&b"This is a package function");
}
}
// 另一个模块
module my_addr::other_module {
use my_addr::access_control;
fun test_access() {
access_control::public_function(); // ✅ 可以调用
// access_control::private_function(); // ❌ 不能调用私有函数
access_control::call_private(); // ✅ 可以调用
}
}
Module 的组织原则
1. 单一职责原则
每个模块应该只负责一个特定的功能领域
// ✅ 好的设计
module my_addr::math {
// 数学运算相关
}
module my_addr::string_utils {
// 字符串处理相关
}
// ❌ 不好的设计
module my_addr::everything {
// 混合了多种功能
}
2. 高内聚,低耦合
模块内部的功能应该紧密相关,模块之间的依赖应该尽量减少
// 高内聚的模块设计
module my_addr::user_management {
struct User has key, store {
id: u64,
name: vector<u8>,
email: vector<u8>,
}
public fun create_user(account: &signer, name: vector<u8>, email: vector<u8>) {
// 用户创建逻辑
}
public fun update_user(account: &signer, name: vector<u8>) {
// 用户更新逻辑
}
public fun delete_user(account: &signer) {
// 用户删除逻辑
}
}
3. 模块依赖管理
使用 use
语句导入其他模块
// 基础模块
module my_addr::math {
public fun add(a: u64, b: u64): u64 { a + b }
public fun subtract(a: u64, b: u64): u64 { a - b }
}
// 依赖基础模块的高级模块
module my_addr::calculator {
use my_addr::math;
public fun calculate(a: u64, b: u64, operation: u8): u64 {
if (operation == 1) {
math::add(a, b)
} else {
math::subtract(a, b)
}
}
}
模块设计模式
1. 工具模块模式
只使用当前函数,不需要状态或存储
module my_addr::utils {
use std::vector;
public fun find_max(numbers: vector<u64>): u64 {
let max = 0;
let i = 0;
let len = vector::length(&numbers);
while (i < len) {
let current = *vector::borrow(&numbers, i);
if (current > max) {
max = current;
};
i = i + 1;
};
max
}
public fun reverse_string(s: vector<u8>): vector<u8> {
let result = vector::empty<u8>();
let i = vector::length(&s);
while (i > 0) {
i = i - 1;
vector::push_back(&mut result, *vector::borrow(&s, i));
};
result
}
}
2. 服务模块模式
提供状态和存储,通常用于管理复杂的业务逻辑
module my_addr::voting_service {
use std::signer;
use std::vector;
struct Vote has key, store {
proposal_id: u64,
voter: address,
choice: bool,
}
struct Proposal has key, store {
id: u64,
title: vector<u8>,
description: vector<u8>,
yes_votes: u64,
no_votes: u64,
is_active: bool,
}
public fun create_proposal(
account: &signer,
title: vector<u8>,
description: vector<u8>
) {
// 创建提案逻辑
}
public fun vote(
account: &signer,
proposal_id: u64,
choice: bool
) {
// 投票逻辑
}
public fun get_proposal_result(proposal_id: u64): (u64, u64) {
// 获取投票结果
(0, 0) // 占位符
}
}
最佳实践
1. 命名规范
// ✅ 好的命名
module my_addr::user_management { }
module my_addr::token_contract { }
module my_addr::voting_system { }
// ❌ 不好的命名
module my_addr::user { }
module my_addr::token { }
module my_addr::vote { }
2. 文档注释
vscode 插件可以读取模块中的文档注释
module my_addr::math_utils {
/// 计算两个数的最大公约数
/// @param a 第一个数
/// @param b 第二个数
/// @return 最大公约数
public fun gcd(a: u64, b: u64): u64 {
if (b == 0) {
a
} else {
gcd(b, a % b)
}
}
}
3. 错误处理
使用 assert!()
进行错误处理 或者 使用 abort 的方式
module my_addr::safe_math {
const EOVERFLOW: u64 = 1;
const EINVALID_INPUT: u64 = 2;
public fun safe_add(a: u64, b: u64): u64 {
assert!(a <= 0xFFFFFFFFFFFFFFFF - b, EOVERFLOW);
a + b
}
public fun safe_div(a: u64, b: u64): u64 {
if (a == 0) {
abort EINVALID_INPUT;
}
a / b
}
}
小结
通过本章的学习,我们了解了:
- Module 的概念:Move 中代码组织的基本单位
- Module 的结构:声明、导入、常量、结构体、函数
- 访问控制:public、private、friend、package 访问修饰符
- 最佳实践:命名、文档、错误处理
Module 是 Move 编程的基础,掌握好模块的设计和组织,将为后续学习更复杂的 Move 概念打下坚实基础。
下一节:3.1 data-types
3.1 基本数据类型
Move 的基本数据类型
Move 是一种静态类型语言,所有变量和表达式都有明确的类型。了解基本数据类型是编写 Move 程序的基础。
整数类型
无符号整数
Move 提供了多种无符号整数类型,用于表示不同范围的整数值:
module my_addr::integer_types {
use std::debug;
#[test]
public fun demonstrate_integers() {
// u8: 8位无符号整数 (0 到 255)
let small_number: u8 = 255;
debug::print(&small_number);
// u16: 16位无符号整数 (0 到 65535)
let medium_number: u16 = 65535;
debug::print(&medium_number);
// u32: 32位无符号整数 (0 到 4,294,967,295)
let large_number: u32 = 4294967295;
debug::print(&large_number);
// u64: 64位无符号整数 (0 到 18,446,744,073,709,551,615)
let huge_number: u64 = 18446744073709551615;
debug::print(&huge_number);
// u128: 128位无符号整数
let massive_number: u128 = 340282366920938463463374607431768211455;
debug::print(&massive_number);
// u256: 256位无符号整数
let enormous_number: u256 = 115792089237316195423570985008687907853269984665640564039457584007913129639935;
debug::print(&enormous_number);
}
// 整数运算示例
#[test]
public fun arithmetic_operations() {
let a: u64 = 10;
let b: u64 = 3;
// 加法
let sum = a + b;
debug::print(&sum); // 13
assert!(sum == 13, 100);
// 减法
let difference = a - b;
debug::print(&difference); // 7
assert!(difference == 7, 101);
// 乘法
let product = a * b;
debug::print(&product); // 30
assert!(product == 30, 102);
// 除法(整数除法)
let quotient = a / b;
debug::print("ient); // 3
assert!(quotient == 3, 103);
// 取余
let remainder = a % b;
debug::print(&remainder); // 1
assert!(remainder == 1, 104);
}
// 整数比较
#[test]
public fun comparison_operations() {
let a: u64 = 10;
let b: u64 = 5;
let is_equal = a == b; // false
let is_not_equal = a != b; // true
let is_greater = a > b; // true
let is_less = a < b; // false
let is_greater_equal = a >= b; // true
let is_less_equal = a <= b; // false
debug::print(&is_equal);
debug::print(&is_not_equal);
debug::print(&is_greater);
debug::print(&is_less);
debug::print(&is_greater_equal);
debug::print(&is_less_equal);
assert!(is_equal == false, 200);
assert!(is_not_equal == true, 201);
assert!(is_greater == true, 202);
assert!(is_less == false, 203);
assert!(is_greater_equal == true, 204);
assert!(is_less_equal == false, 205);
}
}
整数类型选择指南
类型 | 范围 | 用途 |
---|---|---|
u8 | 0-255 | 小数值、状态标志 |
u16 | 0-65535 | 中等数值 |
u32 | 0-4,294,967,295 | 一般用途 |
u64 | 0-18,446,744,073,709,551,615 | 推荐使用 |
u128 | 极大数值 | 金融计算、大数运算 |
u256 | 极大数值 | 密码学、精确计算 |
布尔类型
布尔类型用于表示逻辑值:
module my_addr::boolean_types {
use std::debug;
#[test]
public fun demonstrate_booleans() {
// 布尔字面量
let true_value: bool = true;
let false_value: bool = false;
debug::print(&true_value);
debug::print(&false_value);
assert!(true_value == true, 300);
assert!(false_value == false, 301);
// 布尔运算
let a = true;
let b = false;
let and_result = a && b; // false
let or_result = a || b; // true
let not_result = !a; // false
debug::print(&and_result);
debug::print(&or_result);
debug::print(¬_result);
assert!(and_result == false, 302);
assert!(or_result == true, 303);
assert!(not_result == false, 304);
// 条件表达式
let condition = 10 > 5;
let result = if (condition) { true } else { false };
debug::print(&result);
assert!(result == true, 305);
}
// 布尔函数示例
public fun is_even(number: u64): bool {
number % 2 == 0
}
public fun is_positive(number: u64): bool {
number > 0
}
public fun is_in_range(value: u64, min: u64, max: u64): bool {
value >= min && value <= max
}
}
地址类型
地址类型用于表示区块链上的账户地址:
module my_addr::address_types {
use std::debug;
use std::signer;
#[test(account = @0x1234)]
public fun demonstrate_addresses(account: &signer) {
// 获取签名者地址
let account_addr = signer::address_of(account);
debug::print(&account_addr);
assert!(account_addr == @0x1234, 400);
// 地址字面量
let my_address: address = @0x1234;
let std_address: address = @0x1;
debug::print(&my_address);
debug::print(&std_address);
assert!(my_address == @0x1234, 401);
assert!(std_address == @0x1, 402);
// 地址比较
let is_my_address = account_addr == my_address;
debug::print(&is_my_address);
assert!(is_my_address == true, 403);
}
// 地址验证函数
public fun is_valid_address(addr: address): bool {
// 检查地址是否为零地址
addr != @0x0
}
public fun is_my_address(addr: address): bool {
addr == @0x1234
}
}
向量类型
向量是 Move 中的动态数组:
module my_addr::vector_types {
use std::debug;
use std::vector;
#[test]
public fun demonstrate_vectors() {
// 创建空向量
let empty_vector: vector<u64> = vector::empty<u64>();
assert!(empty_vector.length() == 0, 500);
// 创建带初始值的向量
let numbers: vector<u64> = vector[1, 2, 3, 4, 5];
assert!(numbers.length() == 5, 501);
// 添加元素
numbers.push_back(6);
assert!(numbers.length() == 6, 502);
// 获取向量长度
let length = numbers.length();
debug::print(&length); // 6
assert!(length == 6, 503);
// 访问元素
let first = numbers[0];
let last = numbers[length - 1];
debug::print(&first); // 1
debug::print(&last); // 6
assert!(first == 1, 504);
assert!(last == 6, 505);
// 修改元素
let mut_ref = numbers.borrow_mut(0);
*mut_ref = 10;
assert!(numbers[0] == 10, 506);
// 删除元素
let removed = numbers.pop_back();
debug::print(&removed); // 6
assert!(removed == 6, 507);
assert!(numbers.length() == 5, 508);
// 检查向量是否为空
let is_empty = numbers.is_empty();
debug::print(&is_empty); // false
assert!(is_empty == false, 509);
// 反转向量
numbers.reverse();
debug::print(&numbers); // [5, 4, 3, 2, 10]
assert!(numbers == vector[5, 4, 3, 2, 10], 510);
// 过滤向量
let filtered_numbers = numbers.filter(|x| *x > 2);
debug::print(&filtered_numbers); // [5, 4, 3, 10]
assert!(filtered_numbers == vector[5, 4, 3, 10], 511);
// 映射向量
let mapped_numbers = numbers.map(|x| x * 2);
debug::print(&mapped_numbers); // [10, 8, 6, 4, 20]
assert!(mapped_numbers == vector[10, 8, 6, 4, 20], 512);
// 折叠向量
let sum = numbers.fold(0, |acc, x| acc + x);
debug::print(&sum); // 24
assert!(sum == 24, 513);
}
}
字节向量(字符串)
在 Move 中,字符串实际上是字节向量:
module my_addr::string_types {
use std::debug;
use std::string;
#[test]
public fun demonstrate_strings() {
// 使用 std::string 处理字符串
let hello = string::utf8(b"Hello, Move!");
let empty = string::utf8(b"");
debug::print(&hello);
debug::print(&empty);
assert!(hello.length() == 12, 600);
assert!(empty.length() == 0, 601);
// 字符串长度
let length = hello.length();
debug::print(&length); // 12
assert!(length == 12, 602);
// 子字符串查找
let move_str = string::utf8(b"Move");
let move_index = hello.index_of(&move_str);
debug::print(&move_index); // 7
assert!(move_index == 7, 603);
// 字符串连接
let hello_str = string::utf8(b" Hello");
hello_str.append_utf8(b" World!");
debug::print(&hello_str); // "Hello World!"
assert!(hello_str == string::utf8(b" Hello World!"), 604);
}
}
类型转换
Move 是强类型语言,但支持一些隐式和显式类型转换:
module my_addr::type_conversion {
use std::debug;
#[test]
public fun demonstrate_conversions() {
// 显式转换(小类型到大类型)
let small: u8 = 255;
let medium: u16 = (small as u16); // u8 -> u16
let large: u32 = (medium as u32); // u16 -> u32
let huge: u64 = (large as u64); // u32 -> u64
assert!(medium == 255, 700);
assert!(large == 255, 701);
assert!(huge == 255, 702);
// 显式转换(大类型到小类型)
let big_number: u64 = 255;
let small_number: u8 = (big_number as u8);
debug::print(&small_number); // 255
assert!(small_number == 255, 703);
}
#[test]
#[expected_failure]
fun error_handling() {
// 尝试将一个大类型转换为小类型,Move 会报错
let large_value: u64 = 300;
let small_value: u8 = (large_value as u8); // 在此处发生错误
debug::print(&small_value); // 44
}
}
类型推断
Move 支持类型推断,编译器可以自动推断变量类型:
module my_addr::type_inference {
use std::debug;
public fun demonstrate_inference() {
// 类型推断
let number = 42; // 推断为 u64
let flag = true; // 推断为 bool
let text = b"Hello"; // 推断为 vector<u8>
let list = vector[1, 2, 3]; // 推断为 vector<u64>
// 显式类型声明
let explicit_number: u64 = 42;
let explicit_flag: bool = true;
debug::print(&number);
debug::print(&flag);
debug::print(&text);
debug::print(&list);
}
}
常见错误和最佳实践
1. 整数溢出和下溢
Move 会在运行时检查整数溢出和下溢,如果发生会自动 abort:
module my_addr::overflow_example {
use std::debug;
#[test]
public fun test_normal_operations() {
// 正常的运算不会溢出
let a: u64 = 100;
let b: u64 = 200;
let sum = a + b;
debug::print(&sum); // 300
assert!(sum == 300, 100);
let product = a * b;
debug::print(&product); // 20000
assert!(product == 20000, 101);
}
#[test]
#[expected_failure] // 预期这个测试会失败
public fun test_overflow() {
// 这会导致溢出,Move 会自动 abort
let max_val: u64 = 18446744073709551615; // u64 最大值
let _overflow = max_val + 1; // 溢出,程序会 abort
}
#[test]
#[expected_failure] // 预期这个测试会失败
public fun test_underflow() {
// 这会导致下溢,Move 会自动 abort
let zero: u64 = 0;
let _underflow = zero - 1; // 下溢,程序会 abort
}
#[test]
#[expected_failure] // 预期这个测试会失败
public fun test_division_by_zero() {
// 除零错误,Move 会自动 abort
let a: u64 = 10;
let _result = a / 0; // 除零,程序会 abort
}
}
2. 类型选择指南
module my_addr::type_guidelines {
// ✅ 推荐:使用 u64 作为默认整数类型
public fun calculate_total(items: vector<u64>): u64 {
// 实现
0
}
// ✅ 推荐:使用 vector<u8> 表示字符串
public fun process_name(name: vector<u8>): vector<u8> {
// 实现
name
}
// ✅ 推荐:使用 bool 表示状态
public fun is_valid(value: u64): bool {
value > 0
}
}
小结
通过本章的学习,我们掌握了:
- 整数类型:u8, u16, u32, u64, u128, u256 的使用和选择
- 布尔类型:逻辑运算和条件判断
- 地址类型:区块链地址的表示和操作
- 向量类型:动态数组的创建和操作
- 字符串类型:字节向量的字符串处理
- 类型转换:安全和不安全的类型转换
- 类型推断:让编译器自动推断类型
- 最佳实践:避免溢出、选择合适的类型
这些基本数据类型是 Move 编程的基础,掌握它们将帮助我们更好地理解和编写 Move 程序。
下一节:3.2 结构体 (Struct)
3.2 结构体 (Struct)
什么是结构体
结构体是 Move 中用于定义自定义数据类型的核心概念。它允许将多个不同类型的数据组合成一个逻辑单元,类似于其他编程语言中的类或对象。在 Move 中,结构体是构建复杂数据结构和资源的基础。
结构体字段的私有性
⚠️ 重要概念:在 Move 中,结构体的所有字段都是私有的,这意味着:
- 结构体字段只能在定义该结构体的模块内部直接访问
- 其他模块无法直接读取或修改结构体的字段
- 如果需要在其他模块中访问字段,必须通过公共函数(getter/setter)来实现
- 这种设计确保了数据封装和模块间的清晰边界
结构体的定义
基本语法
module my_addr::struct_basics {
use std::debug;
// 定义一个简单的结构体(命名字段)
struct Person {
name: vector<u8>,
age: u8,
is_student: bool,
}
// 定义一个更复杂的结构体
struct Account {
address: address,
balance: u64,
active: bool,
}
// 包含其他结构体的结构体
struct Company {
name: vector<u8>,
ceo: Person,
employees: vector<Person>,
founded_year: u16,
}
// 🆕 元组结构体(Tuple Struct)- 新特性
struct Point(u64, u64) has copy, drop;
struct Color(u8, u8, u8) has copy, drop;
struct Pair(u64, u8) has copy, drop;
// 元组结构体的使用示例
#[test]
public fun test_tuple_structs() {
// 创建元组结构体
let point = Point(10, 20);
let red_color = Color(255, 0, 0);
let pair = Pair(42, 1);
// 访问元组结构体的字段(通过索引)
let x = point.0; // 第一个字段
let y = point.1; // 第二个字段
let red = red_color.0; // R 值
let green = red_color.1; // G 值
let blue = red_color.2; // B 值
let first = pair.0; // 第一个值
let second = pair.1; // 第二个值
debug::print(&x); // 10
debug::print(&y); // 20
debug::print(&red); // 255
debug::print(&first); // 42
assert!(x == 10, 100);
assert!(y == 20, 101);
assert!(red == 255, 102);
assert!(green == 0, 103);
assert!(blue == 0, 104);
assert!(first == 42, 105);
assert!(second == 1, 106);
}
// 🆕 结构体解构(Destructuring)
#[test]
public fun test_struct_destructuring() {
// 元组结构体解构
let point = Point(100, 200);
let Point(x, y) = point; // 解构赋值
assert!(x == 100, 107);
assert!(y == 200, 108);
let color = Color(128, 64, 32);
let Color(r, g, b) = color; // 解构 RGB 值
assert!(r == 128, 109);
assert!(g == 64, 110);
assert!(b == 32, 111);
let pair = Pair(999, 5);
let Pair(number, boolean_like) = pair; // 解构为不同变量名
assert!(number == 999, 112);
assert!(boolean_like == 5, 113);
debug::print(&x);
debug::print(&y);
debug::print(&r);
debug::print(&number);
}
}
结构体的命名规范
module my_addr::naming_conventions {
// ✅ 必须:使用 PascalCase(首字母大写)
struct UserProfile {
username: vector<u8>,
email: vector<u8>,
}
struct BankAccount {
account_number: u64,
balance: u64,
}
// ✅ 推荐:描述性的名称
struct TokenMetadata {
name: vector<u8>,
symbol: vector<u8>,
decimals: u8,
}
}
创建结构体实例
基本创建方式
module my_addr::struct_creation {
use std::debug;
// 命名字段结构体
struct Point {
x: u64,
y: u64,
}
struct Rectangle {
top_left: Point,
width: u64,
height: u64,
}
// 元组结构体
struct TuplePoint(u64, u64) has copy, drop;
struct RGB(u8, u8, u8) has copy, drop;
#[test]
public fun test_create_structs() {
// 创建命名字段结构体
let origin = Point {
x: 0,
y: 0,
};
let point_a = Point {
x: 10,
y: 20,
};
// 创建包含其他结构体的结构体
let rect = Rectangle {
top_left: origin,
width: 100,
height: 50,
};
// 🆕 创建元组结构体
let tuple_point = TuplePoint(30, 40);
let white_color = RGB(255, 255, 255);
debug::print(&point_a.x); // 10
debug::print(&point_a.y); // 20
debug::print(&rect.width); // 100
debug::print(&tuple_point.0); // 30
debug::print(&tuple_point.1); // 40
debug::print(&white_color.0); // 255
assert!(point_a.x == 10, 100);
assert!(point_a.y == 20, 101);
assert!(rect.width == 100, 102);
assert!(rect.height == 50, 103);
assert!(tuple_point.0 == 30, 104);
assert!(tuple_point.1 == 40, 105);
assert!(white_color.0 == 255, 106);
assert!(white_color.1 == 255, 107);
assert!(white_color.2 == 255, 108);
}
// 使用构造函数创建结构体
public fun new_point(x: u64, y: u64): Point {
Point { x, y }
}
public fun new_rectangle(x: u64, y: u64, width: u64, height: u64): Rectangle {
Rectangle {
top_left: new_point(x, y),
width,
height,
}
}
// 🆕 元组结构体的构造函数
public fun new_tuple_point(x: u64, y: u64): TuplePoint {
TuplePoint(x, y)
}
public fun new_rgb_color(r: u8, g: u8, b: u8): RGB {
RGB(r, g, b)
}
#[test]
public fun test_constructor_functions() {
let point = new_point(5, 15);
let rect = new_rectangle(0, 0, 200, 100);
// 🆕 使用元组结构体构造函数
let tuple_point = new_tuple_point(50, 60);
let blue_color = new_rgb_color(0, 0, 255);
assert!(point.x == 5, 200);
assert!(point.y == 15, 201);
assert!(rect.top_left.x == 0, 202);
assert!(rect.top_left.y == 0, 203);
assert!(rect.width == 200, 204);
assert!(rect.height == 100, 205);
assert!(tuple_point.0 == 50, 206);
assert!(tuple_point.1 == 60, 207);
assert!(blue_color.2 == 255, 208); // 蓝色分量
}
// 🆕 结构体解构的进阶用法
#[test]
public fun test_advanced_destructuring() {
// 命名字段结构体解构
let point = Point { x: 75, y: 125 };
let Point { x, y } = point; // 命名字段解构
assert!(x == 75, 209);
assert!(y == 125, 210);
// 可以使用不同的变量名
let Point { x: coord_x, y: coord_y } = point;
assert!(coord_x == 75, 211);
assert!(coord_y == 125, 212);
// 元组结构体在函数参数中解构
let tuple_point = TuplePoint(88, 99);
let distance = calculate_distance_from_origin(tuple_point);
assert!(distance == 187, 213); // 88 + 99 的简化距离
// 在函数返回值中使用解构
let rgb = create_custom_color();
let RGB(red, green, blue) = rgb;
assert!(red == 255, 214);
assert!(green == 128, 215);
assert!(blue == 0, 216);
debug::print(&coord_x);
debug::print(&coord_y);
debug::print(&distance);
}
// 在函数参数中直接解构
public fun calculate_distance_from_origin(TuplePoint(x, y): TuplePoint): u64 {
x + y // 简化的距离计算
}
// 返回元组结构体供解构使用
public fun create_custom_color(): RGB {
RGB(255, 128, 0) // 橙色
}
}
访问和修改结构体字段
字段访问权限
在 Move 中,结构体字段的访问遵循严格的私有性规则:
- ✅ 模块内部:可以直接访问和修改字段
- ❌ 模块外部:无法直接访问字段,需要通过公共函数
// 文件: my_module.move
module my_addr::my_module {
struct Person {
name: vector<u8>,
age: u8,
}
// ✅ 在同一模块内可以直接访问字段
public fun create_person(name: vector<u8>, age: u8): Person {
Person { name, age }
}
// ✅ 提供公共访问器函数
public fun get_name(person: &Person): vector<u8> {
person.name
}
public fun get_age(person: &Person): u8 {
person.age
}
// ✅ 提供公共修改器函数
public fun set_age(person: &mut Person, new_age: u8) {
person.age = new_age;
}
}
// 文件: other_module.move
module my_addr::other_module {
use my_addr::my_module::{Self, Person};
public fun use_person() {
let person = my_module::create_person(b"Alice", 25);
// ❌ 编译错误!无法直接访问字段
// let name = person.name;
// let age = person.age;
// ✅ 正确:通过公共函数访问
let name = my_module::get_name(&person);
let age = my_module::get_age(&person);
}
}
字段访问
module my_addr::struct_access {
use std::debug;
struct Student {
id: u64,
name: vector<u8>,
grade: u8,
gpa: u64, // 乘以100的GPA,例如 325 表示 3.25
}
#[test]
public fun test_field_access() {
let student = Student {
id: 12345,
name: b"Alice",
grade: 10,
gpa: 385, // 3.85
};
// 读取字段
let student_id = student.id;
let student_name = student.name;
let student_grade = student.grade;
let student_gpa = student.gpa;
debug::print(&student_id); // 12345
debug::print(&student_name); // b"Alice"
debug::print(&student_grade); // 10
debug::print(&student_gpa); // 385
assert!(student_id == 12345, 300);
assert!(student_name == b"Alice", 301);
assert!(student_grade == 10, 302);
assert!(student_gpa == 385, 303);
}
#[test]
public fun test_field_modification() {
let mut student = Student {
id: 12345,
name: b"Bob",
grade: 9,
gpa: 350,
};
// 修改字段
student.grade = 10;
student.gpa = 375;
assert!(student.grade == 10, 400);
assert!(student.gpa == 375, 401);
// 通过引用修改
let grade_ref = &mut student.grade;
*grade_ref = 11;
let gpa_ref = &mut student.gpa;
*gpa_ref = 390;
assert!(student.grade == 11, 402);
assert!(student.gpa == 390, 403);
}
// 🆕 字段访问与解构
#[test]
public fun test_field_access_with_destructuring() {
let student = Student {
id: 54321,
name: b"Charlie",
grade: 12,
gpa: 395,
};
// 使用解构一次性获取多个字段
let Student { id, name, grade, gpa } = student;
assert!(id == 54321, 404);
assert!(name == b"Charlie", 405);
assert!(grade == 12, 406);
assert!(gpa == 395, 407);
// 部分解构,只获取需要的字段
let Student { id: student_id, name: student_name, .. } = student;
assert!(student_id == 54321, 408);
assert!(student_name == b"Charlie", 409);
// 在函数调用中使用解构
let summary = get_student_summary(student);
assert!(summary == b"Charlie:12", 410);
debug::print(&id);
debug::print(&name);
debug::print(&summary);
}
// 函数参数解构示例
public fun get_student_summary(Student { name, grade, .. }: Student): vector<u8> {
// 这里只关心 name 和 grade,忽略其他字段
let mut result = name;
result.append(b":");
// 简化的数字转字符串(实际应用中应该使用适当的转换函数)
if (grade == 9) result.append(b"9")
else if (grade == 10) result.append(b"10")
else if (grade == 11) result.append(b"11")
else if (grade == 12) result.append(b"12")
else result.append(b"?");
result
}
}
结构体方法
module my_addr::struct_methods {
use std::debug;
struct Circle {
center_x: u64,
center_y: u64,
radius: u64,
}
// 构造函数
public fun new_circle(x: u64, y: u64, radius: u64): Circle {
Circle {
center_x: x,
center_y: y,
radius,
}
}
// 获取圆的面积(简化计算,实际应该是 π * r²)
public fun area(circle: &Circle): u64 {
// 简化计算:3 * r * r(近似 π)
3 * circle.radius * circle.radius
}
// 获取圆的周长(简化计算,实际应该是 2 * π * r)
public fun circumference(circle: &Circle): u64 {
// 简化计算:6 * r(近似 2π)
6 * circle.radius
}
// 移动圆心
public fun move_circle(circle: &mut Circle, new_x: u64, new_y: u64) {
circle.center_x = new_x;
circle.center_y = new_y;
}
// 缩放圆
public fun scale_circle(circle: &mut Circle, scale_factor: u64) {
circle.radius = circle.radius * scale_factor;
}
// 检查点是否在圆内(简化计算)
public fun contains_point(circle: &Circle, x: u64, y: u64): bool {
let dx = if (x > circle.center_x) {
x - circle.center_x
} else {
circle.center_x - x
};
let dy = if (y > circle.center_y) {
y - circle.center_y
} else {
circle.center_y - y
};
// 简化的距离计算(实际应该用勾股定理)
dx + dy <= circle.radius
}
#[test]
public fun test_circle_methods() {
let mut circle = new_circle(10, 10, 5);
// 测试面积计算
let area_val = area(&circle);
debug::print(&area_val); // 75 (3 * 5 * 5)
assert!(area_val == 75, 500);
// 测试周长计算
let circumference_val = circumference(&circle);
debug::print(&circumference_val); // 30 (6 * 5)
assert!(circumference_val == 30, 501);
// 测试移动
move_circle(&mut circle, 20, 20);
assert!(circle.center_x == 20, 502);
assert!(circle.center_y == 20, 503);
// 测试缩放
scale_circle(&mut circle, 2);
assert!(circle.radius == 10, 504);
// 测试点包含
let contains = contains_point(&circle, 25, 25);
assert!(contains == true, 505);
let not_contains = contains_point(&circle, 35, 35);
assert!(not_contains == false, 506);
}
}
结构体的复制和移动
复制语义
module my_addr::struct_copy {
use std::debug;
// 具有 copy 能力的结构体
struct Point has copy, drop {
x: u64,
y: u64,
}
// 不具有 copy 能力的结构体
struct UniquePoint has drop {
x: u64,
y: u64,
id: u64,
}
#[test]
public fun test_copy_semantics() {
let point1 = Point { x: 10, y: 20 };
// Point 有 copy 能力,可以被复制
let point2 = point1; // 复制
let point3 = point1; // 再次复制
// 所有三个变量都可以使用
assert!(point1.x == 10, 600);
assert!(point2.x == 10, 601);
assert!(point3.x == 10, 602);
debug::print(&point1);
debug::print(&point2);
debug::print(&point3);
}
#[test]
public fun test_move_semantics() {
let unique_point1 = UniquePoint { x: 5, y: 15, id: 1 };
// UniquePoint 没有 copy 能力,发生移动
let unique_point2 = unique_point1; // 移动
// unique_point1 现在不能再使用
assert!(unique_point2.x == 5, 700);
assert!(unique_point2.y == 15, 701);
assert!(unique_point2.id == 1, 702);
debug::print(&unique_point2);
// debug::print(&unique_point1); // 错误!unique_point1 已被移动
}
public fun clone_point(point: &Point): Point {
Point { x: point.x, y: point.y }
}
#[test]
public fun test_explicit_clone() {
let original = Point { x: 100, y: 200 };
let cloned = clone_point(&original);
assert!(original.x == cloned.x, 800);
assert!(original.y == cloned.y, 801);
debug::print(&original);
debug::print(&cloned);
}
}
嵌套结构体
复杂的嵌套结构
module my_addr::nested_structs {
use std::debug;
use std::vector;
struct Address {
street: vector<u8>,
city: vector<u8>,
country: vector<u8>,
postal_code: vector<u8>,
}
struct Contact {
email: vector<u8>,
phone: vector<u8>,
}
struct Person {
name: vector<u8>,
age: u8,
address: Address,
contact: Contact,
}
struct Company {
name: vector<u8>,
employees: vector<Person>,
headquarters: Address,
}
// 构造函数
public fun new_address(
street: vector<u8>,
city: vector<u8>,
country: vector<u8>,
postal_code: vector<u8>
): Address {
Address { street, city, country, postal_code }
}
public fun new_contact(email: vector<u8>, phone: vector<u8>): Contact {
Contact { email, phone }
}
public fun new_person(
name: vector<u8>,
age: u8,
address: Address,
contact: Contact
): Person {
Person { name, age, address, contact }
}
public fun new_company(
name: vector<u8>,
headquarters: Address
): Company {
Company {
name,
employees: vector::empty<Person>(),
headquarters,
}
}
// 操作方法
public fun add_employee(company: &mut Company, person: Person) {
company.employees.push_back(person);
}
public fun get_employee_count(company: &Company): u64 {
company.employees.length()
}
public fun get_company_city(company: &Company): vector<u8> {
company.headquarters.city
}
#[test]
public fun test_nested_structs() {
// 创建地址
let home_address = new_address(
b"123 Main St",
b"New York",
b"USA",
b"10001"
);
let office_address = new_address(
b"456 Business Ave",
b"San Francisco",
b"USA",
b"94105"
);
// 创建联系方式
let contact = new_contact(
b"alice@example.com",
b"+1-555-0123"
);
// 创建人员
let alice = new_person(
b"Alice Johnson",
30,
home_address,
contact
);
// 创建公司
let mut tech_company = new_company(
b"Tech Innovations Inc.",
office_address
);
// 添加员工
add_employee(&mut tech_company, alice);
// 验证
assert!(get_employee_count(&tech_company) == 1, 900);
assert!(get_company_city(&tech_company) == b"San Francisco", 901);
assert!(tech_company.employees[0].name == b"Alice Johnson", 902);
assert!(tech_company.employees[0].age == 30, 903);
assert!(tech_company.employees[0].address.city == b"New York", 904);
debug::print(&tech_company.name);
debug::print(&get_employee_count(&tech_company));
}
}
结构体的能力 (Abilities)
四种基本能力
module my_addr::struct_abilities {
use std::debug;
// 1. copy: 可以被复制
struct Copyable has copy, drop {
value: u64,
}
// 2. drop: 可以被丢弃(销毁)
struct Droppable has drop {
data: vector<u8>,
}
// 3. store: 可以被存储在全局存储中
struct Storable has store {
content: vector<u8>,
}
// 4. key: 可以作为全局存储的键
struct Resource has key {
id: u64,
data: vector<u8>,
}
// 组合能力
struct FlexibleStruct has copy, drop, store {
name: vector<u8>,
value: u64,
}
struct CompleteStruct has copy, drop, store, key {
id: u64,
name: vector<u8>,
data: vector<u8>,
}
#[test]
public fun test_copyable() {
let original = Copyable { value: 42 };
let copy1 = original; // 复制
let copy2 = original; // 再次复制
assert!(original.value == 42, 1000);
assert!(copy1.value == 42, 1001);
assert!(copy2.value == 42, 1002);
debug::print(&original);
debug::print(©1);
debug::print(©2);
}
#[test]
public fun test_droppable() {
let droppable = Droppable { data: b"Hello" };
assert!(droppable.data == b"Hello", 1100);
// droppable 在函数结束时自动被丢弃
}
public fun create_flexible(): FlexibleStruct {
FlexibleStruct {
name: b"Flexible",
value: 100,
}
}
#[test]
public fun test_flexible() {
let flex1 = create_flexible();
let flex2 = flex1; // copy
let flex3 = flex1; // copy again
assert!(flex1.name == b"Flexible", 1200);
assert!(flex2.name == b"Flexible", 1201);
assert!(flex3.name == b"Flexible", 1202);
assert!(flex1.value == 100, 1203);
assert!(flex2.value == 100, 1204);
assert!(flex3.value == 100, 1205);
}
}
泛型结构体
参数化结构体
module my_addr::generic_structs {
use std::debug;
use std::vector;
use std::option::{Self, Option};
// 命名字段泛型结构体
struct Box<T> has copy, drop, store {
value: T,
}
struct Pair<T, U> has copy, drop, store {
first: T,
second: U,
}
struct Container<T> has drop, store {
items: vector<T>,
}
// 🆕 元组结构体泛型
struct TuplePair<T, U>(T, U) has copy, drop, store;
struct Triple<T, U, V>(T, U, V) has copy, drop, store;
struct Wrapper<T>(T) has copy, drop, store;
// 约束泛型参数
struct Comparable<T: copy + drop> has copy, drop, store {
value: T,
}
// 构造函数
public fun new_box<T>(value: T): Box<T> {
Box { value }
}
public fun new_pair<T, U>(first: T, second: U): Pair<T, U> {
Pair { first, second }
}
public fun new_container<T>(): Container<T> {
Container { items: vector::empty<T>() }
}
// 🆕 元组结构体构造函数
public fun new_tuple_pair<T, U>(first: T, second: U): TuplePair<T, U> {
TuplePair(first, second)
}
public fun new_triple<T, U, V>(first: T, second: U, third: V): Triple<T, U, V> {
Triple(first, second, third)
}
public fun new_wrapper<T>(value: T): Wrapper<T> {
Wrapper(value)
}
// 操作方法
public fun get_box_value<T>(box: &Box<T>): &T {
&box.value
}
public fun set_box_value<T>(box: &mut Box<T>, value: T) {
box.value = value;
}
public fun get_first<T, U>(pair: &Pair<T, U>): &T {
&pair.first
}
public fun get_second<T, U>(pair: &Pair<T, U>): &U {
&pair.second
}
// 🆕 元组结构体访问方法
public fun get_tuple_first<T, U>(pair: &TuplePair<T, U>): &T {
&pair.0
}
public fun get_tuple_second<T, U>(pair: &TuplePair<T, U>): &U {
&pair.1
}
public fun get_wrapper_value<T>(wrapper: &Wrapper<T>): &T {
&wrapper.0
}
public fun add_item<T>(container: &mut Container<T>, item: T) {
container.items.push_back(item);
}
public fun get_item_count<T>(container: &Container<T>): u64 {
container.items.length()
}
public fun get_item<T>(container: &Container<T>, index: u64): &T {
&container.items[index]
}
#[test]
public fun test_generic_box() {
// 数字盒子
let mut number_box = new_box<u64>(42);
assert!(*get_box_value(&number_box) == 42, 1300);
set_box_value(&mut number_box, 100);
assert!(*get_box_value(&number_box) == 100, 1301);
// 字符串盒子
let string_box = new_box<vector<u8>>(b"Hello");
assert!(*get_box_value(&string_box) == b"Hello", 1302);
// 布尔盒子
let bool_box = new_box<bool>(true);
assert!(*get_box_value(&bool_box) == true, 1303);
debug::print(get_box_value(&number_box));
debug::print(get_box_value(&string_box));
debug::print(get_box_value(&bool_box));
}
#[test]
public fun test_generic_pair() {
let pair = new_pair<u64, vector<u8>>(123, b"ABC");
assert!(*get_first(&pair) == 123, 1400);
assert!(*get_second(&pair) == b"ABC", 1401);
let bool_pair = new_pair<bool, bool>(true, false);
assert!(*get_first(&bool_pair) == true, 1402);
assert!(*get_second(&bool_pair) == false, 1403);
debug::print(get_first(&pair));
debug::print(get_second(&pair));
}
#[test]
public fun test_generic_container() {
let mut number_container = new_container<u64>();
add_item(&mut number_container, 10);
add_item(&mut number_container, 20);
add_item(&mut number_container, 30);
assert!(get_item_count(&number_container) == 3, 1500);
assert!(*get_item(&number_container, 0) == 10, 1501);
assert!(*get_item(&number_container, 1) == 20, 1502);
assert!(*get_item(&number_container, 2) == 30, 1503);
let mut string_container = new_container<vector<u8>>();
add_item(&mut string_container, b"First");
add_item(&mut string_container, b"Second");
assert!(get_item_count(&string_container) == 2, 1504);
assert!(*get_item(&string_container, 0) == b"First", 1505);
assert!(*get_item(&string_container, 1) == b"Second", 1506);
debug::print(&get_item_count(&number_container));
debug::print(&get_item_count(&string_container));
}
// 🆕 测试元组结构体泛型
#[test]
public fun test_tuple_generics() {
// 元组对
let tuple_pair = new_tuple_pair<u64, vector<u8>>(456, b"XYZ");
assert!(*get_tuple_first(&tuple_pair) == 456, 1600);
assert!(*get_tuple_second(&tuple_pair) == b"XYZ", 1601);
// 三元组
let triple = new_triple<bool, u64, vector<u8>>(true, 789, b"Triple");
assert!(triple.0 == true, 1602);
assert!(triple.1 == 789, 1603);
assert!(triple.2 == b"Triple", 1604);
// 包装器
let wrapper = new_wrapper<u64>(999);
assert!(*get_wrapper_value(&wrapper) == 999, 1605);
debug::print(get_tuple_first(&tuple_pair));
debug::print(&triple.1);
debug::print(get_wrapper_value(&wrapper));
}
// 🆕 泛型结构体解构
#[test]
public fun test_generic_destructuring() {
// 泛型元组结构体解构
let tuple_pair = new_tuple_pair<u64, bool>(777, true);
let TuplePair(number, flag) = tuple_pair;
assert!(number == 777, 1606);
assert!(flag == true, 1607);
let triple = new_triple<u8, u16, u32>(255, 65535, 4294967295);
let Triple(small, medium, large) = triple;
assert!(small == 255, 1608);
assert!(medium == 65535, 1609);
assert!(large == 4294967295, 1610);
let wrapper = new_wrapper<vector<u8>>(b"Wrapped");
let Wrapper(content) = wrapper;
assert!(content == b"Wrapped", 1611);
// 泛型命名字段结构体解构
let box_value = new_box<u64>(888);
let Box { value } = box_value;
assert!(value == 888, 1612);
let pair_value = new_pair<bool, vector<u8>>(false, b"Test");
let Pair { first, second } = pair_value;
assert!(first == false, 1613);
assert!(second == b"Test", 1614);
debug::print(&number);
debug::print(&content);
debug::print(&value);
}
// 在泛型函数中使用解构
public fun extract_first<T, U>(TuplePair(first, _): TuplePair<T, U>): T {
first
}
public fun extract_wrapper_content<T>(Wrapper(content): Wrapper<T>): T {
content
}
#[test]
public fun test_generic_destructuring_functions() {
let pair = new_tuple_pair<u64, vector<u8>>(123, b"ABC");
let first_value = extract_first(pair);
assert!(first_value == 123, 1615);
let wrapper = new_wrapper<bool>(true);
let wrapped_value = extract_wrapper_content(wrapper);
assert!(wrapped_value == true, 1616);
debug::print(&first_value);
debug::print(&wrapped_value);
}
}
实际应用示例
Token 元数据结构
module my_addr::token_example {
use std::debug;
use std::string::{Self, String};
use std::option::{Self, Option};
struct TokenMetadata has copy, drop, store {
name: String,
symbol: String,
decimals: u8,
icon_url: Option<String>,
project_url: Option<String>,
}
struct TokenInfo has key {
metadata: TokenMetadata,
total_supply: u64,
max_supply: Option<u64>,
}
struct Account has key {
balance: u64,
}
// 构造函数
public fun create_token_metadata(
name: vector<u8>,
symbol: vector<u8>,
decimals: u8,
icon_url: Option<vector<u8>>,
project_url: Option<vector<u8>>
): TokenMetadata {
TokenMetadata {
name: string::utf8(name),
symbol: string::utf8(symbol),
decimals,
icon_url: option::map(icon_url, |url| string::utf8(url)),
project_url: option::map(project_url, |url| string::utf8(url)),
}
}
// 获取器方法
public fun get_name(metadata: &TokenMetadata): String {
metadata.name
}
public fun get_symbol(metadata: &TokenMetadata): String {
metadata.symbol
}
public fun get_decimals(metadata: &TokenMetadata): u8 {
metadata.decimals
}
public fun get_icon_url(metadata: &TokenMetadata): Option<String> {
metadata.icon_url
}
public fun get_project_url(metadata: &TokenMetadata): Option<String> {
metadata.project_url
}
#[test]
public fun test_token_metadata() {
let metadata = create_token_metadata(
b"My Token",
b"MTK",
8,
option::some(b"https://example.com/icon.png"),
option::some(b"https://myproject.com")
);
assert!(get_name(&metadata) == string::utf8(b"My Token"), 2000);
assert!(get_symbol(&metadata) == string::utf8(b"MTK"), 2001);
assert!(get_decimals(&metadata) == 8, 2002);
assert!(option::is_some(&get_icon_url(&metadata)), 2003);
assert!(option::is_some(&get_project_url(&metadata)), 2004);
debug::print(&metadata.name);
debug::print(&metadata.symbol);
debug::print(&metadata.decimals);
}
}
最佳实践和常见错误
1. 结构体设计原则
module my_addr::best_practices {
use std::vector;
// ✅ 好的设计:字段相关且内聚
struct User {
id: u64,
username: vector<u8>,
email: vector<u8>,
created_at: u64,
}
// ✅ 提供访问器函数(getter)
public fun get_user_id(user: &User): u64 {
user.id
}
public fun get_username(user: &User): vector<u8> {
user.username
}
public fun get_email(user: &User): vector<u8> {
user.email
}
// ✅ 提供修改器函数(setter)
public fun update_email(user: &mut User, new_email: vector<u8>) {
// 可以在这里添加验证逻辑
assert!(new_email.length() > 0, 1);
user.email = new_email;
}
// ✅ 好的设计:明确的职责
struct BankAccount {
account_number: u64,
balance: u64,
owner: address,
}
// ✅ 封装业务逻辑
public fun deposit(account: &mut BankAccount, amount: u64) {
assert!(amount > 0, 2);
account.balance = account.balance + amount;
}
public fun withdraw(account: &mut BankAccount, amount: u64) {
assert!(amount > 0, 3);
assert!(account.balance >= amount, 4);
account.balance = account.balance - amount;
}
public fun get_balance(account: &BankAccount): u64 {
account.balance
}
// ❌ 不好的设计:字段不相关
struct MixedData {
user_name: vector<u8>,
account_balance: u64,
random_number: u64,
is_weekend: bool,
}
// ✅ 好的设计:使用合适的能力
struct Config has copy, drop, store {
max_users: u64,
fee_rate: u64,
}
// ✅ 好的设计:资源类型
struct Coin has key {
value: u64,
}
// 🆕 解构的最佳实践
// ✅ 推荐:使用解构简化代码
public fun process_user_data(user: User): (u64, vector<u8>) {
let User { id, username, .. } = user; // 只解构需要的字段
(id, username)
}
// ✅ 推荐:在函数参数中直接解构
public fun validate_bank_account(BankAccount { balance, owner, .. }: &BankAccount): bool {
*balance > 0 && *owner != @0x0
}
// ✅ 推荐:使用解构进行模式匹配
public fun categorize_config(config: Config): vector<u8> {
let Config { max_users, fee_rate } = config;
if (max_users > 1000 && fee_rate < 100) {
b"Enterprise"
} else if (max_users > 100) {
b"Professional"
} else {
b"Basic"
}
}
// ❌ 避免:过度解构,影响可读性
/*
public fun bad_destructuring(User { id: user_identification_number, username: user_display_name, email: user_contact_email, created_at: user_registration_timestamp }: User) {
// 变量名过长,影响可读性
}
*/
// ✅ 推荐:简洁的变量名
public fun good_destructuring(User { id, username, email, created_at }: User) {
// 清晰简洁
}
}
2. 构造函数模式
module my_addr::constructor_patterns {
use std::vector;
struct Student {
id: u64,
name: vector<u8>,
scores: vector<u64>,
}
// ✅ 基本构造函数
public fun new_student(id: u64, name: vector<u8>): Student {
Student {
id,
name,
scores: vector::empty<u64>(),
}
}
// ✅ 带验证的构造函数
public fun new_student_validated(id: u64, name: vector<u8>): Student {
assert!(id > 0, 100);
assert!(name.length() > 0, 101);
Student {
id,
name,
scores: vector::empty<u64>(),
}
}
// ✅ 构建器模式
public fun new_student_builder(): StudentBuilder {
StudentBuilder {
id: 0,
name: vector::empty<u8>(),
scores: vector::empty<u64>(),
}
}
struct StudentBuilder {
id: u64,
name: vector<u8>,
scores: vector<u64>,
}
public fun with_id(builder: StudentBuilder, id: u64): StudentBuilder {
StudentBuilder { id, ..builder }
}
public fun with_name(builder: StudentBuilder, name: vector<u8>): StudentBuilder {
StudentBuilder { name, ..builder }
}
public fun build(builder: StudentBuilder): Student {
assert!(builder.id > 0, 200);
assert!(builder.name.length() > 0, 201);
Student {
id: builder.id,
name: builder.name,
scores: builder.scores,
}
}
}
3. 常见错误和解决方案
module my_addr::common_mistakes {
struct Data has drop {
values: vector<u64>,
}
// ❌ 错误:直接修改不可变引用
/*
public fun wrong_modify(data: &Data) {
data.values.push_back(10); // 编译错误!
}
*/
// ✅ 正确:使用可变引用
public fun correct_modify(data: &mut Data, value: u64) {
data.values.push_back(value);
}
// ❌ 错误:返回局部变量的引用
/*
public fun wrong_return(): &Data {
let data = Data { values: vector::empty<u64>() };
&data // 编译错误!
}
*/
// ✅ 正确:返回拥有所有权的值
public fun correct_return(): Data {
Data { values: vector::empty<u64>() }
}
// ✅ 正确:接受引用参数并返回计算结果
public fun get_sum(data: &Data): u64 {
let mut sum = 0;
let mut i = 0;
while (i < data.values.length()) {
sum = sum + data.values[i];
i = i + 1;
};
sum
}
}
// 演示跨模块访问错误
module my_addr::field_access_errors {
struct Person has drop {
name: vector<u8>,
age: u8,
}
public fun create_person(name: vector<u8>, age: u8): Person {
Person { name, age }
}
// ✅ 正确:提供公共访问函数
public fun get_name(person: &Person): vector<u8> {
person.name
}
public fun get_age(person: &Person): u8 {
person.age
}
}
module my_addr::user_module {
use my_addr::field_access_errors::{Self, Person};
public fun use_person_correctly() {
let person = field_access_errors::create_person(b"Alice", 25);
// ❌ 错误:尝试直接访问其他模块的结构体字段
/*
let name = person.name; // 编译错误!
let age = person.age; // 编译错误!
*/
// ✅ 正确:通过公共函数访问
let name = field_access_errors::get_name(&person);
let age = field_access_errors::get_age(&person);
}
}
小结
通过本章的学习,我们掌握了:
- 结构体定义:如何定义和命名结构体(命名字段和元组结构体)
- 实例创建:不同的创建和初始化方式
- 字段操作:访问和修改结构体字段(包括索引访问)
- 🆕 结构体解构:使用模式匹配解构结构体,简化代码
- 方法定义:为结构体定义相关的操作函数
- 复制和移动:理解值语义和引用语义
- 嵌套结构体:构建复杂的数据结构
- 能力系统:copy、drop、store、key 四种能力
- 泛型结构体:参数化的结构体定义(命名字段和元组形式)
- 实际应用:Token 元数据等实际场景
- 最佳实践:设计原则和常见错误避免
结构体特性对比
特性 | 命名字段结构体 | 元组结构体 |
---|---|---|
定义语法 | struct Point { x: u64, y: u64 } | struct Point(u64, u64) |
创建语法 | Point { x: 10, y: 20 } | Point(10, 20) |
字段访问 | point.x , point.y | point.0 , point.1 |
解构语法 | let Point { x, y } = point | let Point(x, y) = point |
部分解构 | let Point { x, .. } = point | 不支持部分解构 |
函数参数解构 | fn(Point { x, y }: Point) | fn(Point(x, y): Point) |
适用场景 | 复杂数据结构,字段含义明确 | 简单数据组合,临时数据结构 |
可读性 | 高(字段名称清晰) | 中等(需要记住字段顺序) |
解构的使用场景
✅ 函数参数解构 - 直接在参数中解构,简化函数体
✅ 模式匹配 - 根据结构体内容进行条件判断
✅ 批量赋值 - 一次性提取多个字段值
✅ 部分解构 - 只提取需要的字段,忽略其他字段
✅ 泛型解构 - 在泛型函数中使用解构提高代码复用性
结构体是 Move 中构建复杂数据类型的基础,掌握它们将帮助我们更好地设计和实现 Move 程序的数据模型。
上一节:3.1 基本数据类型
下一节:3.3 引用和借用
结构体最佳实践
函数
本章介绍 Move 语言中的函数相关内容。
资源
本章介绍 Move 语言中的资源类型。
泛型与能力
本章介绍 Move 语言中的泛型和能力(Abilities)。
测试
本章介绍 Move 语言的测试相关内容。
9.1 枚举类型(Enum)
Move 的枚举实现
Move 没有原生 enum,但可以用 sum type(联合体)模式实现。
枚举的定义与使用
module my_addr::enum_example {
struct MyEnum has copy, drop, store {
tag: u8, // 0: A, 1: B
value_a: u64,
value_b: bool,
}
// ...示例代码...
}
枚举的常见用法
- 状态机
- 错误码
- 事件类型
小结
枚举模式让 Move 代码更具表达力。
10.1 跨合约导入与调用
Move 跨模块/合约调用基础
导入其他模块
module my_addr::import_example {
use 0x1::coin;
// ...示例代码...
}
跨模块调用函数
module my_addr::call_example {
use 0x1::coin;
public fun call_coin(): u64 {
coin::balance_of(@0x1)
}
}
跨合约调用的注意事项
- 访问控制
- 能力(abilities)
- 依赖管理
小结
跨合约调用是 Move 合约协作的基础。
示例
本章收录了 Move 语言的实用示例代码。