1.1 Move 语言简介
什么是 Aptos Move?
Aptos Move 是一种专门为区块链设计的编程语言,由 Facebook(现 Meta)开发,最初为 Diem 项目设计。Aptos Move 语言的核心设计理念是资源安全和类型安全。
现由 Aptos 区块链使用,并扩展语法并重写编译器和升级虚拟机
Move 语言的特点
🔒 资源安全
- 资源模型:Move 将数字资产视为"资源",确保资产不能被复制或意外销毁
- 线性类型系统:每个资源只能被移动,不能被复制
- 所有权模型:明确的资源所有权和转移机制
🛡️ 类型安全
- 静态类型检查:编译时进行类型检查,避免运行时错误
- 内存安全:没有空指针、悬空引用等内存安全问题
- 形式化验证:支持数学证明程序正确性
📦 模块化设计
- Module 系统:代码组织在模块中,便于管理和重用
- 能力系统:通过能力(Abilities)控制类型的行为
- 泛型支持:支持泛型编程,提高代码复用性
Move 与其他语言的对比
特性 | Move | Solidity | Rust |
---|---|---|---|
资源安全 | ✅ 原生支持 | ❌ 需要手动管理 | ✅ 所有权系统 |
类型安全 | ✅ 静态类型 | ⚠️ 动态类型 | ✅ 静态类型 |
区块链专用 | ✅ 专为区块链设计 | ✅ 以太坊专用 | ❌ 通用语言 |
学习曲线 | 🟡 中等 | 🟢 简单 | 🔴 陡峭 |
Move 的应用场景
🪙 代币合约
#![allow(unused)] fn main() { // 简单的代币合约示例 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:版本控制
安装步骤
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` 文件:
```rust
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. 模块声明
#![allow(unused)] fn main() { module hello_blockchain::message { }
module
:关键字,声明这是一个模块hello_blockchain::message
:模块的完整名称hello_blockchain
:地址(在 Move.toml 中定义)message
:模块名称
2. 导入标准库
#![allow(unused)] fn main() { 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. 资源定义
#![allow(unused)] fn main() { 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. 常量定义
#![allow(unused)] fn main() { const ENO_MESSAGE: u64 = 0; }
const
:声明一个常量ENO_MESSAGE
:表示没有消息的错误代码
5. 函数定义
#![allow(unused)] fn main() { #[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. 函数实现
#![allow(unused)] fn main() { 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
:获取并返回消息内容
#![allow(unused)] fn main() { 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. 测试函数
#![allow(unused)] fn main() { #[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. 注释
#![allow(unused)] fn main() { // 单行注释 /// 文档注释 /* 多行注释 */ }
2. 变量声明
#![allow(unused)] fn main() { let x = 10; // 类型推断 let y: u64 = 20; // 显式类型声明 }
3. 条件语句
#![allow(unused)] fn main() { if (condition) { // 代码块 } else { // 代码块 } }
4. 循环
#![allow(unused)] fn main() { while (condition) { // 循环体 } }
5. 函数调用
#![allow(unused)] fn main() { module_name::function_name(arg1, arg2); }
6. 断言
#![allow(unused)] fn main() { assert!(condition, error_code); }
常见错误和调试
调试技巧
- 使用
debug::print()
输出调试信息 - 在测试中使用
assert!()
验证结果
小结
通过这个简单的 Hello World 程序,我们学习了:
- 模块结构:如何声明和组织模块
- 函数定义:如何定义和调用函数
- 测试编写:如何为代码编写测试
- 调试技巧:如何使用调试输出
下一节:2.1 Module 概念与结构
2.1 Module 概念与结构
什么是 Module?
在 Move 中,Module(模块) 是代码组织的基本单位。模块类似于其他编程语言中的类或包,它封装了相关的函数、结构体和常量。
Module 的核心概念
1. 命名空间
#![allow(unused)] fn main() { module my_addr::math { // 模块内容 } }
my_addr
:地址(Address),类似于包名math
:模块名my_addr::math
:完整的模块标识符
2. 封装性
- 模块内的代码可以访问模块内的所有内容
- 其他模块只能访问被标记为
public
的内容 - 提供了良好的封装和抽象
3. 可重用性
- 模块可以被其他模块导入和使用
- 支持模块的组合和扩展
Module 的基本结构
完整的模块示例
#![allow(unused)] fn main() { 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. 模块声明
#![allow(unused)] fn main() { module <address>::<module_name> { // 模块内容 } }
2. 导入语句
#![allow(unused)] fn main() { use std::signer; // 导入标准库模块 use my_addr::math; // 导入自定义模块 }
3. 常量定义
常量定义只能定义基础类型变量
const <name>: <type> = <value>;
#![allow(unused)] fn main() { const ERROR_CODE: u64 = 1; const MAX_BALANCE: u64 = 1000000; }
4. 结构体定义
#![allow(unused)] fn main() { struct MyStruct has key, store { field1: u64, field2: vector<u8>, } }
5. 函数定义
#![allow(unused)] fn main() { public fun public_function() { // 公共函数 } fun private_function() { // 私有函数 } }
Module 的访问控制
访问修饰符
1. public
#![allow(unused)] fn main() { public fun public_function() { // 可以被其他模块调用 } }
2. 默认(私有)
#![allow(unused)] fn main() { fun private_function() { // 只能在模块内部调用 } }
3. public(friend)
需要在模块声明中指定友元模块
#![allow(unused)] fn main() { public(friend) fun friend_function() { // 只能被友元模块调用 } }
4. public(package)
无需声明 Friend 模块,当前包内可以直接访问
public (package) fun package_function() {
// 只能被同一包内的模块调用
}
访问控制示例
#![allow(unused)] fn main() { 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. 单一职责原则
每个模块应该只负责一个特定的功能领域
#![allow(unused)] fn main() { // ✅ 好的设计 module my_addr::math { // 数学运算相关 } module my_addr::string_utils { // 字符串处理相关 } // ❌ 不好的设计 module my_addr::everything { // 混合了多种功能 } }
2. 高内聚,低耦合
模块内部的功能应该紧密相关,模块之间的依赖应该尽量减少
#![allow(unused)] fn main() { // 高内聚的模块设计 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
语句导入其他模块
#![allow(unused)] fn main() { // 基础模块 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. 工具模块模式
只使用当前函数,不需要状态或存储
#![allow(unused)] fn main() { 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. 服务模块模式
提供状态和存储,通常用于管理复杂的业务逻辑
#![allow(unused)] fn main() { 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. 命名规范
#![allow(unused)] fn main() { // ✅ 好的命名 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 插件可以读取模块中的文档注释
#![allow(unused)] fn main() { 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 的方式
#![allow(unused)] fn main() { 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 提供了多种无符号整数类型,用于表示不同范围的整数值:
#![allow(unused)] fn main() { 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 | 极大数值 | 密码学、精确计算 |
布尔类型
布尔类型用于表示逻辑值:
#![allow(unused)] fn main() { 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 } } }
地址类型
地址类型用于表示区块链上的账户地址:
#![allow(unused)] fn main() { 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 中的动态数组:
#![allow(unused)] fn main() { 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 中,字符串实际上是字节向量:
#![allow(unused)] fn main() { 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 是强类型语言,但支持一些隐式和显式类型转换:
#![allow(unused)] fn main() { 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 支持类型推断,编译器可以自动推断变量类型:
#![allow(unused)] fn main() { 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:
#![allow(unused)] fn main() { 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. 类型选择指南
#![allow(unused)] fn main() { 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)来实现
- 这种设计确保了数据封装和模块间的清晰边界
结构体的定义
基本语法
#![allow(unused)] fn main() { 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); } } }
结构体的命名规范
#![allow(unused)] fn main() { 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, } } }
创建结构体实例
基本创建方式
#![allow(unused)] fn main() { module my_addr::struct_creation { use std::debug; // 命名字段结构体 // 为了方便演示,为 struct 增加 copy 和 drop 的能力 struct Point has copy, drop{ x: u64, y: u64, } // 为了方便演示,为 struct 增加 copy 和 drop 的能力 struct Rectangle has copy, drop { 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 RGB(red, green, blue) = create_custom_color(); assert!(red == 255, 214); assert!(green == 128, 215); assert!(blue == 0, 216); debug::print(&coord_x); debug::print(&coord_y); } // 返回元组结构体供解构使用 public fun create_custom_color(): RGB { RGB(255, 128, 0) // 橙色 } } }
访问和修改结构体字段
字段访问权限
在 Move 中,结构体字段的访问遵循严格的私有性规则:
- ✅ 模块内部:可以直接访问和修改字段
- ❌ 模块外部:无法直接访问字段,需要通过公共函数
#![allow(unused)] fn main() { // 文件: my_module.move module my_addr::my_module { // 为了方便演示,为 struct 增加 drop 的能力 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 } // ✅ 提供公共修改器函数 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; 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); } } }
字段访问
#![allow(unused)] fn main() { module my_addr::struct_access { use std::debug; // 为了方便演示,为 struct 增加 copy 和 drop 的能力 struct Student has copy, drop{ 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 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); debug::print(&id); debug::print(&name); } } }
结构体方法
在为结构体编写代码时,可以使用 self 作为参数名,使得编译器可以自动识别当前函数为结构体方法。
并可以使用 .
语法,使用函数 circle.move_circle(20, 20);
否则只能写为 move_circle(&mut circle, 20, 20);
#![allow(unused)] fn main() { module my_addr::struct_methods { use std::debug; // 为了方便演示,为 struct 增加 copy 和 drop 的能力 struct Circle has copy, drop{ 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(self: &Circle): u64 { // 简化计算:3 * r * r(近似 π) 3 * self.radius * self.radius } // 获取圆的周长(简化计算,实际应该是 2 * π * r) public fun circumference(self: &Circle): u64 { // 简化计算:6 * r(近似 2π) 6 * self.radius } // 移动圆心 public fun move_circle(self: &mut Circle, new_x: u64, new_y: u64) { self.center_x = new_x; self.center_y = new_y; } // 缩放圆 public fun scale_circle(self: &mut Circle, scale_factor: u64) { self.radius *= scale_factor; } // 检查点是否在圆内(简化计算) public fun contains_point(self: &Circle, x: u64, y: u64): bool { let dx = if (x > self.center_x) { x - self.center_x } else { self.center_x - x }; let dy = if (y > self.center_y) { y - self.center_y } else { self.center_y - y }; // 简化的距离计算(实际应该用勾股定理) dx + dy <= self.radius } #[test] public fun test_circle_methods() { let circle = new_circle(10, 10, 5); // 测试面积计算 let area_val = circle.area(); debug::print(&area_val); // 75 (3 * 5 * 5) assert!(area_val == 75, 500); // 测试周长计算 let circumference_val = circle.circumference(); debug::print(&circumference_val); // 30 (6 * 5) assert!(circumference_val == 30, 501); // 测试移动 circle.move_circle(20, 20); assert!(circle.center_x == 20, 502); assert!(circle.center_y == 20, 503); // 测试缩放 circle.scale_circle(2); assert!(circle.radius == 10, 504); // 测试点包含 let contains = circle.contains_point(25, 25); assert!(contains == true, 505); let not_contains = circle.contains_point(35, 35); assert!(not_contains == false, 506); } } }
结构体的复制和移动
复制语义
#![allow(unused)] fn main() { module my_addr::struct_copy { #[test_only] 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); } } }
嵌套结构体
复杂的嵌套结构
#![allow(unused)] fn main() { module my_addr::nested_structs { #[test_only] use std::debug; use std::vector; // 为了方便演示,为 struct 增加 drop 的能力 struct Address has drop { street: vector<u8>, city: vector<u8>, country: vector<u8>, postal_code: vector<u8>, } // 为了方便演示,为 struct 增加 drop 的能力 struct Contact has drop { email: vector<u8>, phone: vector<u8>, } // 为了方便演示,为 struct 增加 drop 的能力 struct Person has drop { name: vector<u8>, age: u8, address: Address, contact: Contact, } // 为了方便演示,为 struct 增加 drop 的能力 struct Company has drop { 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(self: &mut Company, person: Person) { self.employees.push_back(person); } public fun get_employee_count(self: &Company): u64 { self.employees.length() } public fun get_company_city(self: &Company): vector<u8> { self.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 tech_company = new_company( b"Tech Innovations Inc.", office_address ); // 添加员工 tech_company.add_employee(alice); // 验证 assert!(tech_company.get_employee_count() == 1, 900); assert!(tech_company.get_company_city() == 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(&tech_company.get_employee_count()); } } }
结构体的能力 (Abilities)
四种基本能力
#![allow(unused)] fn main() { module my_addr::struct_abilities { #[test_only] use std::debug; // 1. copy: 可以被复制 struct Copyable has copy { 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); // 由于 Copyable 结构体未实现了 drop 能力,所以不能进行丢弃 // 需要手动解构结构体 // 可以将结构体中的值赋值给变量 let Copyable { value: _value } = original; // 也可以使用 _ 忽略某个值 let Copyable { value: _ } = copy1; // 也可以使用 .. 忽略所有值 let Copyable { .. } = copy2; } #[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); } } }
泛型结构体
参数化结构体
#![allow(unused)] fn main() { module my_addr::generic_structs { #[test_only] use std::debug; use std::vector; // ======================================== // 泛型结构体定义 // ======================================== // 1. 命名字段泛型结构体 - Box<T> // 这是一个通用的容器结构体,可以存储任意类型的值 // 添加了 copy, drop, store 能力以便于演示和测试 struct Box<T> has copy, drop, store { value: T, // 存储任意类型的值 } // 2. 双类型泛型结构体 - Pair<T, U> // 可以存储两个不同类型的值,类似于元组但使用命名字段 struct Pair<T, U> has copy, drop, store { first: T, // 第一个值 second: U, // 第二个值 } // 3. 容器泛型结构体 - Container<T> // 一个可以存储多个同类型元素的容器 struct Container<T> has drop, store { items: vector<T>, // 使用 vector 存储多个元素 } // 4. 元组结构体泛型 - 使用位置索引访问字段 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; // 单值包装器 // 5. 带约束的泛型结构体 - Comparable<T> // 泛型参数 T 必须具有 copy 和 drop 能力 struct Comparable<T: copy + drop> has copy, drop, store { value: T, } // ======================================== // 构造函数 // ======================================== // 创建 Box 实例 public fun new_box<T>(value: T): Box<T> { Box { value } } // 创建 Pair 实例 public fun new_pair<T, U>(first: T, second: U): Pair<T, U> { Pair { first, second } } // 创建空的 Container 实例 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) } // ======================================== // 访问和操作方法 // ======================================== // Box 相关操作 public fun get_box_value<T>(self: &Box<T>): &T { &self.value // 返回对内部值的引用 } public fun set_box_value<T: drop>(self: &mut Box<T>, value: T) { self.value = value; // 设置新的值 } // Pair 相关操作 public fun get_first<T, U>(self: &Pair<T, U>): &T { &self.first // 获取第一个值 } public fun get_second<T, U>(self: &Pair<T, U>): &U { &self.second // 获取第二个值 } // 元组结构体访问方法 - 使用位置索引 public fun get_tuple_first<T, U>(self: &TuplePair<T, U>): &T { &self.0 // 访问第一个元素(索引 0) } public fun get_tuple_second<T, U>(self: &TuplePair<T, U>): &U { &self.1 // 访问第二个元素(索引 1) } public fun get_wrapper_value<T>(self: &Wrapper<T>): &T { &self.0 // 访问包装的值 } // Container 相关操作 public fun add_item<T>(self: &mut Container<T>, item: T) { self.items.push_back(item); // 向容器添加元素 } public fun get_item_count<T>(self: &Container<T>): u64 { self.items.length() // 获取容器中元素的数量 } public fun get_item<T>(self: &Container<T>, index: u64): &T { &self.items[index] // 根据索引获取元素 } // ======================================== // 测试函数 // ======================================== #[test] public fun test_generic_box() { // 测试不同类型的 Box 结构体 // 1. 数字盒子 - 存储 u64 类型 let number_box = new_box<u64>(42); assert!(*number_box.get_box_value() == 42, 1300); // 修改盒子中的值 number_box.set_box_value(100); assert!(*number_box.get_box_value() == 100, 1301); // 2. 字符串盒子 - 存储 vector<u8> 类型 let string_box = new_box<vector<u8>>(b"Hello"); assert!(*string_box.get_box_value() == b"Hello", 1302); // 3. 布尔盒子 - 存储 bool 类型 let bool_box = new_box<bool>(true); assert!(*bool_box.get_box_value() == true, 1303); // 打印所有盒子的值进行验证 debug::print(number_box.get_box_value()); debug::print(string_box.get_box_value()); debug::print(bool_box.get_box_value()); } #[test] public fun test_generic_pair() { // 测试 Pair 结构体 - 可以存储两个不同类型的值 // 创建包含数字和字符串的对 let pair = new_pair<u64, vector<u8>>(123, b"ABC"); // 验证第一个和第二个值 assert!(*pair.get_first() == 123, 1400); assert!(*pair.get_second() == b"ABC", 1401); // 创建包含两个布尔值的对 let bool_pair = new_pair<bool, bool>(true, false); assert!(*bool_pair.get_first() == true, 1402); assert!(*bool_pair.get_second() == false, 1403); // 打印值进行验证 debug::print(pair.get_first()); debug::print(pair.get_second()); } #[test] public fun test_generic_container() { // 测试 Container 结构体 - 可以存储多个同类型的元素 // 1. 数字容器 let number_container = new_container<u64>(); // 向容器添加多个数字 number_container.add_item(10); number_container.add_item(20); number_container.add_item(30); // 验证容器的大小和内容 assert!(number_container.get_item_count() == 3, 1500); assert!(*number_container.get_item(0) == 10, 1501); assert!(*number_container.get_item(1) == 20, 1502); assert!(*number_container.get_item(2) == 30, 1503); // 2. 字符串容器 let string_container = new_container<vector<u8>>(); string_container.add_item(b"First"); string_container.add_item(b"Second"); // 验证字符串容器 assert!(string_container.get_item_count() == 2, 1504); assert!(*string_container.get_item(0) == b"First", 1505); assert!(*string_container.get_item(1) == b"Second", 1506); // 打印容器大小进行验证 debug::print(&number_container.get_item_count()); debug::print(&string_container.get_item_count()); } #[test] public fun test_tuple_generics() { // 测试元组结构体泛型 - 使用位置索引访问字段 // 1. 元组对 - 使用位置索引访问 let tuple_pair = new_tuple_pair<u64, vector<u8>>(456, b"XYZ"); assert!(*tuple_pair.get_tuple_first() == 456, 1600); assert!(*tuple_pair.get_tuple_second() == b"XYZ", 1601); // 2. 三元组 - 直接使用位置索引 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); // 第三个元素 // 3. 包装器 - 包装单个值 let wrapper = new_wrapper<u64>(999); assert!(*wrapper.get_wrapper_value() == 999, 1605); // 打印值进行验证 debug::print(tuple_pair.get_tuple_first()); debug::print(&triple.1); debug::print(wrapper.get_wrapper_value()); } #[test] public fun test_generic_destructuring() { // 测试泛型结构体的解构 - 将结构体分解为单独的变量 // 1. 元组结构体解构 let tuple_pair = new_tuple_pair<u64, bool>(777, true); let TuplePair(number, flag) = tuple_pair; // 解构为 number 和 flag assert!(number == 777, 1606); assert!(flag == true, 1607); // 2. 三元组解构 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); // 3. 包装器解构 let wrapper = new_wrapper<vector<u8>>(b"Wrapped"); let Wrapper(content) = wrapper; // 解构为 content assert!(content == b"Wrapped", 1611); // 4. 命名字段结构体解构 let box_value = new_box<u64>(888); let Box { value } = box_value; // 使用字段名解构 assert!(value == 888, 1612); // 5. Pair 结构体解构 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); } #[test] public fun test_generic_destructuring_functions() { // 测试使用函数访问元组结构体的值 // 使用函数获取元组对的第一个值 let pair = new_tuple_pair<u64, vector<u8>>(123, b"ABC"); let first_value = pair.get_tuple_first(); assert!(*first_value == 123, 1615); // 使用函数获取包装器的值 let wrapper = new_wrapper<bool>(true); let wrapped_value = wrapper.get_wrapper_value(); assert!(*wrapped_value == true, 1616); // 打印获取的值进行验证 debug::print(first_value); debug::print(wrapped_value); } } }
实际应用示例
Token 元数据结构
看了一大堆基础,可以来看看模拟一个 Token Metadata 是如何记录的吧
#![allow(unused)] fn main() { module my_addr::token_example { #[test_only] use std::debug; use std::string::{Self, String}; use std::option::Option; // ======================================== // 代币相关结构体定义 // ======================================== // 代币元数据结构体 - 存储代币的基本信息 // 具有 copy, drop, store 能力,可以在函数间传递和存储 struct TokenMetadata has copy, drop, store { name: String, // 代币名称,如 "Bitcoin" symbol: String, // 代币符号,如 "BTC" decimals: u8, // 小数位数,如 8 表示支持 8 位小数 icon_url: Option<String>, // 代币图标 URL(可选) project_url: Option<String>, // 项目官网 URL(可选) } // 代币信息结构体 - 存储代币的完整信息 // 具有 key 能力,可以作为全局存储的资源 struct TokenInfo has key { metadata: TokenMetadata, // 代币元数据 total_supply: u64, // 当前总供应量 max_supply: Option<u64>, // 最大供应量(可选,None 表示无上限) } // 账户结构体 - 存储用户账户信息 // 具有 key 能力,可以作为全局存储的资源,但是不能带有 copy 能力,防止被复制 struct Account has key { balance: u64, // 账户余额 } // ======================================== // 构造函数 // ======================================== // 创建代币元数据 // 参数使用 vector<u8> 类型,然后转换为 String 类型 public fun create_token_metadata( name: vector<u8>, // 代币名称的字节数组 symbol: vector<u8>, // 代币符号的字节数组 decimals: u8, // 小数位数 icon_url: Option<vector<u8>>, // 图标 URL 的字节数组(可选) project_url: Option<vector<u8>> // 项目 URL 的字节数组(可选) ): TokenMetadata { TokenMetadata { name: string::utf8(name), // 将字节数组转换为 UTF-8 字符串 symbol: string::utf8(symbol), // 将字节数组转换为 UTF-8 字符串 decimals, // 使用 map 函数处理可选值:如果有值则转换为字符串,否则保持 None icon_url: icon_url.map(|url| string::utf8(url)), project_url: project_url.map(|url| string::utf8(url)), } } // ======================================== // 获取器方法(Getter Methods) // ======================================== // 获取代币名称 public fun get_name(self: &TokenMetadata): String { self.name } // 获取代币符号 public fun get_symbol(self: &TokenMetadata): String { self.symbol } // 获取代币小数位数 public fun get_decimals(self: &TokenMetadata): u8 { self.decimals } // 获取代币图标 URL(可能为 None) public fun get_icon_url(self: &TokenMetadata): Option<String> { self.icon_url } // 获取项目官网 URL(可能为 None) public fun get_project_url(self: &TokenMetadata): Option<String> { self.project_url } // ======================================== // 测试函数 // ======================================== #[test] public fun test_token_metadata() { // 创建一个代币元数据实例进行测试 // 创建包含所有字段的代币元数据 let metadata = create_token_metadata( b"My Token", // 代币名称 b"MTK", // 代币符号 8, // 8 位小数 option::some(b"https://example.com/icon.png"), // 图标 URL option::some(b"https://myproject.com") // 项目 URL ); // 验证所有字段的值是否正确 assert!(metadata.get_name() == string::utf8(b"My Token"), 2000); assert!(metadata.get_symbol() == string::utf8(b"MTK"), 2001); assert!(metadata.get_decimals() == 8, 2002); assert!(metadata.get_icon_url().is_some(), 2003); // 验证图标 URL 存在 assert!(metadata.get_project_url().is_some(), 2004); // 验证项目 URL 存在 // 打印元数据信息进行验证 debug::print(&metadata.name); debug::print(&metadata.symbol); debug::print(&metadata.decimals); } } }
小结
通过本章的学习,我们掌握了:
- 结构体定义:如何定义和命名结构体(命名字段和元组结构体)
- 实例创建:不同的创建和初始化方式
- 字段操作:访问和修改结构体字段(包括索引访问)
- 🆕 结构体解构:使用模式匹配解构结构体,简化代码
- 方法定义:为结构体定义相关的操作函数
- 复制和移动:理解值语义和引用语义
- 嵌套结构体:构建复杂的数据结构
- 能力系统: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 | 不支持部分解构 |
适用场景 | 复杂数据结构,字段含义明确 | 简单数据组合,临时数据结构 |
可读性 | 高(字段名称清晰) | 中等(需要记住字段顺序) |
解构的使用场景
✅ 函数参数解构 - 直接在参数中解构,简化函数体
✅ 模式匹配 - 根据结构体内容进行条件判断
✅ 批量赋值 - 一次性提取多个字段值
✅ 部分解构 - 只提取需要的字段,忽略其他字段
✅ 泛型解构 - 在泛型函数中使用解构提高代码复用性
结构体是 Move 中构建复杂数据类型的基础,掌握它们将帮助我们更好地设计和实现 Move 程序的数据模型。
上一节:3.1 基本数据类型
下一节:3.3 引用和借用
3.3 引用和可变引用
引用概述
在 Move 语言中,引用(References)是一种强大的工具,允许我们在不获取值的所有权的情况下访问和操作数据。引用提供了安全的内存访问机制,防止悬空指针和数据竞争。
在前面的教程中,你也应该也看到了 &
和 &mut
的用法,这就是不可变引用和可变引用
🔗 引用的类型
Move 语言支持两种类型的引用:
- 不可变引用 (
&T
) - 只读访问 - 可变引用 (
&mut T
) - 读写访问
不可变引用 (&T)
不可变引用允许你只读访问数据,不能修改被引用的值。
基本语法
#![allow(unused)] fn main() { let ref = &value; // 创建不可变引用 let field_ref = &obj.field; // 字段引用 }
示例代码
#![allow(unused)] fn main() { #[test] public fun test_immutable_references() { let number = 42; let number_ref = &number; // 创建不可变引用 // 通过解引用访问值 assert!(*number_ref == 42, 100); // number_ref 是只读的,不能修改 // *number_ref = 50; // 这会导致编译错误 } }
可变引用 (&mut T)
可变引用允许你读取和修改被引用的数据。
基本语法
#![allow(unused)] fn main() { let mut_ref = &mut value; // 创建可变引用 let field_mut_ref = &mut obj.field; // 字段可变引用 }
示例代码
#![allow(unused)] fn main() { #[test] public fun test_mutable_references() { let number = 42; let number_mut_ref = &mut number; // 创建可变引用 // 读取值 assert!(*number_mut_ref == 42, 200); // 修改值 *number_mut_ref = 100; assert!(*number_mut_ref == 100, 201); } }
结构体字段引用
可以创建指向结构体特定字段的引用。
字段不可变引用
#![allow(unused)] fn main() { public fun get_name(person: &Person): &String { &person.name // 返回字段的不可变引用 } }
字段可变引用
#![allow(unused)] fn main() { public fun update_age(person: &mut Person, new_age: u8) { person.age = new_age; // 直接修改字段 // 或使用引用 let age_ref = &mut person.age; *age_ref = new_age; } }
引用规则和限制
1. 借用规则
Move 遵循严格的借用规则,确保内存安全:
- 规则1:在任何给定时间,对于任何值,要么有一个可变引用,要么有多个不可变引用
- 规则2:引用必须始终有效(不能悬空)
2. 生命周期
#![allow(unused)] fn main() { public fun reference_lifetime_example() { let number = 42; let ref1 = &number; // 创建不可变引用 let ref2 = &number; // 可以有多个不可变引用 // let mut_ref = &mut number; // 错误!不能同时有可变和不可变引用 assert!(*ref1 == 42, 300); assert!(*ref2 == 42, 301); } }
3. 可变性传播
#![allow(unused)] fn main() { struct SomeStruct has key, store { field: u64, } public fun mutability_propagation(data: &mut SomeStruct, new_value: u64) { let field_ref = &mut data.field; // 继承可变性 *field_ref = new_value; } }
函数参数中的引用
传递引用
#![allow(unused)] fn main() { // 接受不可变引用 public fun read_only_function(data: &SomeStruct): u64 { data.some_field } // 接受可变引用 public fun modify_function(data: &mut SomeStruct) { data.some_field = 100; } }
返回引用
#![allow(unused)] fn main() { // 返回不可变引用 public fun get_field_ref(data: &SomeStruct): &u64 { &data.some_field } // 返回可变引用 public fun get_field_mut_ref(data: &mut SomeStruct): &mut u64 { &mut data.some_field } }
引用和所有权
移动 vs 借用
#![allow(unused)] fn main() { public fun ownership_vs_borrowing() { let data = SomeStruct { field: 42 }; // 移动所有权 let owned = data; // data 不再可用 // 借用(创建引用) let data2 = SomeStruct { field: 100 }; let borrowed = &data2; // data2 仍然可用 // 可以继续使用 data2 let field_value = data2.field; } }
借用检查器
Move 的借用检查器在编译时验证所有引用使用的安全性:
#![allow(unused)] fn main() { public fun borrow_checker_example() { let value = 42; let ref1 = &value; // 这是安全的 let ref2 = &value; assert!(*ref1 == *ref2, 400); // 创建可变引用会结束所有不可变引用的生命周期 let mut_ref = &mut value; *mut_ref = 100; // ref1 和 ref2 在这里不再有效 } }
实际应用示例
1. 数据验证
#![allow(unused)] fn main() { public fun validate_person(person: &Person): bool { person.age > 0 && !person.name.is_empty() } }
2. 条件修改
#![allow(unused)] fn main() { public fun conditional_update(person: &mut Person, condition: bool) { if (condition) { person.age = person.age + 1; } } }
3. 嵌套结构体访问
#![allow(unused)] fn main() { public fun access_nested_field(company: &Company): &String { &company.ceo.person.name } public fun update_nested_field(company: &mut Company, new_name: String) { company.ceo.person.name = new_name; } }
常见模式
1. 可选字段处理
#![allow(unused)] fn main() { public fun process_optional_field(data: &SomeStruct) { if (data.optional_field.is_some()) { let value = & data.optional_field; // 处理值 } } }
2. 引用链
#![allow(unused)] fn main() { public fun reference_chain(data: &mut ComplexStruct) { let level1 = &mut data.level1; let level2 = &mut level1.level2; let level3 = &mut level2.level3; *level3 = new_value; } }
3. 临时引用
#![allow(unused)] fn main() { public fun temporary_reference(data: &mut SomeStruct) { { let temp_ref = &mut data.field; *temp_ref = compute_new_value(); } // temp_ref 的生命周期在这里结束 // 可以创建新的引用 let another_ref = &data.field; } }
性能考虑
引用的优势
- 节省 Gas - 引用在运行时开销较少,gas 较低
- 避免复制 - 大型数据结构不需要复制
高级主题
1. 引用类型推断
#![allow(unused)] fn main() { public fun type_inference_example() { let data = SomeStruct { field: 42 }; let ref_data = &data; // 类型自动推断为 &SomeStruct // 显式类型注解 let explicit_ref: &SomeStruct = &data; } }
2. 泛型引用
#![allow(unused)] fn main() { public fun generic_reference<T>(data: &T): &T { data // 返回相同的引用 } public fun generic_mutable_reference<T>(data: &mut T) { // 对泛型可变引用的操作 } }
3. 引用作为结构体字段
注意:Move 不允许将引用存储在结构体中,但可以作为函数参数和返回值使用。
#![allow(unused)] fn main() { // 这是不允许的 // struct BadStruct { // ref_field: &u64, // 编译错误 // } // 正确的做法是使用函数参数 public fun work_with_reference(data: &SomeStruct) { // 在函数作用域内使用引用 } }
总结
引用是 Move 语言中的核心概念,提供了安全、高效的内存访问机制。理解引用的规则和最佳实践对于编写安全、高性能的 Move 代码至关重要。
关键要点
- 安全性:引用系统防止悬空指针和数据竞争
- 节省 Gas:无需复制值,节省 Gas
- 灵活性:支持复杂的数据访问模式
- 编译时检查:所有引用安全性在编译时验证
下一步
在下一节中,我们将学习 Move 语言中的函数定义和调用,以及如何在函数中有效使用引用。
相关链接:
函数
本章介绍 Move 语言中的函数相关内容。
资源
本章介绍 Move 语言中的资源类型。
泛型与能力
本章介绍 Move 语言中的泛型和能力(Abilities)。
测试
本章介绍 Move 语言的测试相关内容。
9.1 枚举类型(Enum)
Move 的枚举实现
Move 没有原生 enum,但可以用 sum type(联合体)模式实现。
枚举的定义与使用
#![allow(unused)] fn main() { 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 跨模块/合约调用基础
导入其他模块
#![allow(unused)] fn main() { module my_addr::import_example { use 0x1::coin; // ...示例代码... } }
跨模块调用函数
#![allow(unused)] fn main() { module my_addr::call_example { use 0x1::coin; public fun call_coin(): u64 { coin::balance_of(@0x1) } } }
跨合约调用的注意事项
- 访问控制
- 能力(abilities)
- 依赖管理
小结
跨合约调用是 Move 合约协作的基础。
示例
本章收录了 Move 语言的实用示例代码。