本文介绍了Sui Move语言及其特性,Sui Move是基于Rust的智能合约语言,强调资源管理和所有权,具有对象中心存储的特点,通过严格的类型系统和能力(如copy、drop、store和key)确保数据完整性和安全性。同时,文章还介绍了Sui Move中的数据类型、Object对象,以及函数和参数。
我最近尝试了一种新的智能合约语言,MOVE。
这种语言的灵感来自 Rust,因此对于 Solidity 开发者来说并不直观。
然而,用它来构建非常有趣。
在本系列文章中,我旨在介绍 Move 语言及其特性。
让我们直接开始吧。
MOVE 是一种编程语言,最初由 Facebook(现在的 Meta)开发,作为 Libra(后来的 Diem)区块链项目的一部分。
在 Diem 关闭后,MOVE 在 Aptos 和 Sui 等区块链生态系统中找到了新的生命。它被设计为一种安全、灵活且面向资源的语言,专门用于区块链用例。
目前,我们有不同的 Move 变体,如 Aptos Move 和 Sui Move。
💡
加粗SUI-Move加粗 可能与原始 Move 存在显着差异,以支持 Sui 的以对象为中心的架构和并行事务处理。诸如 dynamic fields(动态字段) 和 object-first storage(对象优先存储) 之类的术语是 SUI-MOVE 中的根本性变化,它似乎更针对以资产为中心、高吞吐量和对象优先的执行进行了优化。
在本文中,我们将专门解读 Sui Move。
Move 是一种编译型的、严格类型的语言。严格类型意味着每个变量、结构体和函数都必须在编译时明确知道其类型。
没有隐式强制转换,没有鸭子类型。虽然 Move 是严格类型的,但它允许通过使用类型参数实现泛型类型,从而在保持严格类型的同时实现一定程度的灵活性。
它也深受 Rust 的启发。例如所有权系统、借用检查器、移动语义、无垃圾收集器的内存安全等概念。
需要记住的术语:
术语 | 含义 |
---|---|
Package(包) | 部署单元;一组一起发布在链上的模块。 |
Module(模块) | 包内的编译单元;定义结构体、函数和常量。 |
Struct(结构体) | 用户定义的类型,可以有字段和 abilities(能力);可以表示对象或内存中的值。 |
Entry Function(入口函数) | 用户可在交易中调用的特殊公共函数;具有严格的参数规则。 |
Abilities(能力) | 编译时标签(copy(复制), drop(丢弃), store(存储), key(键)),控制类型的行为方式。 |
Object(对象) | 具有 key(键) 能力和 id: UID 的结构体;持久存储在链上并由 Sui 跟踪。 |
UID | 表示对象标识的特殊内置类型。Key(键) 结构体必需。 |
Move Semantics(移动语义) | 默认行为,值在赋值时被移动(消耗),而不是被复制。 |
Reference(引用) (&, &mut) | 值的借用——不可变 (&) 或可变 (&mut)——强制执行严格的别名使用。 |
Type Abilities(类型能力) | copy(复制), drop(丢弃), store(存储), key(键) – 启用值复制、销毁、存储和对象 ID 使用。 |
包是发布在链上一个或多个模块的集合。单个包可以包含一个或多个模块。模块是包的构建块。
有趣的是,模块或包不处理存储,因此我们不能只定义不同的字符串或整数,并期望它们存储在链上。
相反,包和模块只处理数据(对象)应该如何表现。
💡 虽然我们可以定义一个常量不变的值,但它们会直接嵌入到字节码中。这些只能为原始类型指定,并且只能用作只读常量。
包在发布时会变成不可变的对象,并被分配一个唯一的 ID,就像 Ethereum 中的智能合约地址一样。
包对象可以升级,但本文不讨论。
由于存储不是由包处理的,因此我们在 Move 中有一个命令式字符来处理存储,即对象。
包定义逻辑和结构,而对象保存数据和状态。
加粗我们将在本文后面学习有关对象的内容。加粗
在我们研究对象之前,我们应该了解 Sui Move 中的所有数据类型。
以下是有关数据类型的一些简要说明。
我们可以通过三种方式访问 Sui-Move 提供的类型。
内置类型:
bool
、无符号整数(u8
、u64
等)和 address
。push_back
、length
、pop_back
等方法,我们需要标准库中的 vector 模块。注意: 虽然 vector 本身是原始类型,但其他类似集合的功能(例如 Maps(映射) 或 Sets(集合))并没有作为原始类型内置到语言中。 相反,它们是在 Move 标准库中作为库实现的。
Sui 框架:
UID
、ID
、TxContext
以及与对象、包升级等相关的操作。它还包含作为依赖项的标准库。 默认情况下,此框架会添加到我们项目的 move.toml
依赖项中。标准库:
string
、BitVector
、Option<T>
等类型。此外,它还提供了大量功能来处理所有类型,包括内置类型。Sui 框架和标准库都有一些隐式导入。 这表明我们不需要显式导入某些内容,例如标准库中的 std::option
、std::vector
以及 Sui 框架中的 tx_context
、object
、transfer
等。
我们还有用户定义的类型,例如 结构体和枚举 -(solidity 开发者熟悉的术语)。
结构体:
public struct StructExample has key, store, copy, drop {
value: u64,
}
枚举
Result
,它可以是 Ok(value)
或 Err(message)
。你可以在创建时选择一种变体。枚举对于表达选项或结果很有用,但它们不能用作 Sui 对象。/// 定义各种字符串段。
public enum Segment has copy, drop {
/// 空变体,无值。
Empty,
/// 具有值的变体(位置样式)。
String(String),
/// 具有命名字段的变体。
Special {
content: vector<u8>,
encoding: u8, // 编码标签。
},
}
/// 实例化
/// 构造一个 `Empty` 段。
public fun new_empty(): Segment { Segment::Empty }
/// 构造一个带有 `str` 值的 `String` 段。
public fun new_string(str: String): Segment { Segment::String(str) }
/// 构造一个带有 `content` 和 `encoding` 值的 `Special` 段。
public fun new_special(content: vector<u8>, encoding: u8): Segment {
Segment::Special {
content,
encoding,
}
}
如果其中一些内容让你感到陌生,请不要担心——稍后我会对其进行分解。
Move 类型可以分配有 4 种 abilities(能力)。
简而言之,move 中的 abilities(能力),决定了特定类型允许执行的操作。
让我们简要地谈谈每一个。
copy(复制)
sui 函数中的所有内容都保留在内存中,除非写入存储。 与 solidity 不同,没有 calldata 的概念。在 Sui Move 中,我们不会在存储或内存之间进行选择。
copy(复制)
能力,我们将无法执行以下操作 👇🏾let student = StudentID { ... };
let a = student;
let b = student; // 错误:已移动
drop(丢弃)
drop(丢弃)
的类型可以被忽略或销毁。module example::drop_demo;
public struct TempData has drop {
value: u64,
}
public fun make_and_discard() {
let _ = TempData { value: 42 };
// 没有错误 — TempData 具有 `drop(丢弃)`,因此可以被忽略
}
store(存储)
store(存储)
确定一个值是否可以存在于全局存储中。 这在处理从函数传输或返回对象时非常重要。store(存储)
。store(存储)
能力。让我们考虑一个例子来清楚地理解这一点:
SimpleData
具有 store(存储)
能力,因为我们将其嵌入到 object(对象)(具有 key(键)
能力的 DataHolder
)中。store(存储)
,这意味着它的所有字段都应该具有 store(存储)
,因此 inner
结构体也需要具有 store(存储)
能力。 /// 具有存储能力的结构体
public struct SimpleData has store {
value: u64,
}
/// 包含 SimpleData 的对象
public struct DataHolder has key {
id: object::UID,
data: SimpleData, // 由于 SimpleData 存储在一个对象中,因此它必须具有 `store(存储)` 能力
}
/// 类似情况
public struct Inner has store {
value: u64,
}
public struct Outer has store {
nested: Inner,
}
// 如果从模块中返回,则结构体需要存储
public struct ReturnableData has store {
value: u64,
}
public entry fun create(): ReturnableData {
ReturnableData { value: 10 }
}
key(键)
UID
),并且由 Sui 运行时跟踪所有权、版本控制等。store(存储)
,但不必一定具有 key(键)
能力。u64
或 string
这样的内置类型默认已经具有 copy(复制)
、drop(丢弃)
和 store(存储)
能力,这就是为什么我们没有明确地写任何东西。 但是任何应该位于具有 key(键)
的另一个结构体中的结构体都应该具有 store(存储)
能力。💡 原始类型默认具有此处提到的所有四种 abilities(能力),因此我们不会明确提及它们。 b. 但是,结构体/枚举需要开发人员明确提及 abilities(能力)。
为了编写可重用、灵活的模块,我们经常需要类型参数。
让我们来谈谈 Move 如何允许我们使用泛型和 phantom types(幽灵类型) 来做到这一点。
在跳到代码之前,还有一个有趣的功能需要学习,即“泛型”。
你将在我们在下一部分中谈到的 move 包中看到泛型的使用。 如果你看到诸如“Type Parameters(类型参数)”或“Type Arguments(类型实参)”之类的术语,请不要感到困惑。 这些是泛型的其他名称。
将泛型视为未知类型的占位符。 意味着你可以使用泛型定义一个函数,并且顾名思义,这将成为一个适用于任何 type(类型)
的泛型函数。
或者你可以定义一个泛型结构体,这意味着该结构体可以具有你分配的任何 type(类型)
。 让我们用一个小例子来理解一下。
这定义了一个可以容纳任何类型 T 的容器。
public struct Container<T> has drop {
value: T,
}
这从 T 类型的值创建一个新的 Container<T>。
<T>
是某种类型的占位符(例如,u64
、String
等)。T
会被你传入的实际类型替换。public fun new<T>(value: T): Container<T> {
Container { value }
}
泛型的好处在于它们可以不使用。 以下是上述函数和结构体的示例用法。
let c1 = new(10); // 推断为 Container<u8>
let c2 = new<u64>(100); // 显式类型
let c3: Container<String> = new("hi".to_string());
请注意,我们可以将 u8、u64 以及字符串传递给同一个函数和结构体。
有时,你想用一种类型标记一个结构体以用于识别或逻辑,而无需实际存储该类型的任何数据。 这就是 phantom types(幽灵类型) 的用武之地。
以下是 Coin 系统中的一个示例:
public struct TreasuryCap<phantom T> has key, store {
id: UID,
total_supply: Supply<T>,
}
phantom T
告诉编译器:“我正在跟踪类型 T
,但我没有存储该类型的值。”T
),而无需在内存中携带它。TreasuryCap<USDC>
不会错误地铸造 DAI
。现在我们了解了类型和 abilities(能力),现在是研究对象的时候了。
在 Sui 中,对象是一等公民。 与帐户或余额等传统术语不同,SUI 使用对象来存储任何东西。
对象具有:
ObjectID
- 从事务上下文中派生的对象的唯一标识符。我们使用 struct(结构体)
类型定义对象。 需要注意的是,这个结构体应该具有 key(键)
能力,并且 id: UID
作为第一个字段。
public struct MyCounter has key {
id: UID,
value: u64,
}
在上面的例子中:
MyCounter
是一个自定义对象类型。id: UID
赋予它一个唯一的身份。value: u64
是我们要存储的数据。进一步扩展对象的所有权。 每个对象都有所有者,这是 Sui 链的内置功能。 让我们讨论三种类型:
到目前为止,我们已经讨论了 类型、对象和 abilities(能力) 等。 为了使一切协同工作,我们需要执行逻辑,即函数。
让我们看看函数是如何工作的,以及它们如何驱动 Move 模块中的逻辑。
函数是任何 Sui Move 模块的构建块。 每个操作——创建对象、更新状态、转移所有权、铸造Token——都是在函数内部完成的。
函数使用 fun
关键字声明:
fun add(x: u64, y: u64): u64
{
x + y
}
关键字 | 谁可以调用它? | 用于 |
---|---|---|
fun |
仅在同一个模块内 | 内部逻辑 |
public fun |
任何模块 | 链上模块到模块的调用 |
public entry fun |
最终用户(钱包、前端、CLI) | 事务入口点 |
public(package) fun |
同一个包中的任何模块 | 跨包模块的内部 API |
<visibility>? <entry>? fun <name><type_params>(<params>): <return_type> {
// 函数体
}
visibility
:可选(public
, public(package)
)
entry
:可选 (只有最终用户可调用的函数才能使用 entry
)
<type_params>
:可选的泛型类型参数,如 <T>
params
: 逗号分隔的参数
return_type
:单个类型、元组或 ()
表示无返回
public entry fun transfer_coin<T>(
coin: Coin<T>,
recipient: address,
ctx: &mut TxContext
) {
transfer::public_transfer(coin, recipient);
}
entry
→ 可以在事务中调用public
→ 从模块外部可见T
→ 泛型类型参数ctx
→ 任何改变状态的函数始终需要&T |
值的不可变引用——无法修改它。 |
---|---|
&mut T |
可变引用——允许更改传递的对象。 |
T (按值传递) |
仅对具有复制/丢弃能力的原始类型或小型结构体有效。 |
vector<T> |
如果 T 是有效类型(例如 vector<u64> ),则可以直接传递。 |
TxContext |
必须是 &mut TxContext ,始终最后传递。 状态更改所必需。 |
u64
、bool
、address
等)TxContext
&mut TxContext
有效:
public entry fun create(ctx: &mut TxContext) { ... }
无效:
public entry fun bad(ctx: TxContext) { ... } // 不是引用
函数可以返回元组:
fun get_name_and_age(): (string::String, u8) {
(string::utf8(b"John"), 25)
}
let (name, age) = get_name_and_age();
好的,我们在本文中介绍了很多细节。
我们了解到 Move 提供了一种独特而强大的方法来构建区块链应用程序,它结合了 Rust 启发式功能的稳健性以及区块链特定的资源管理和所有权重点。
Sui-Move 的突出特点之一是它强调以对象为中心的存储,其中对象是一等公民。 这种从传统的基于帐户的模型的范式转变使得可以更动态地处理资产和数据。 此外,严格的类型系统和 copy(复制)
、drop(丢弃)
、store(存储)
和 key(键)
等 abilities(能力) 确保开发人员保持对数据完整性和安全性的细粒度控制。
此外,通过利用泛型和 phantom types(幽灵类型),开发人员可以编写灵活且可重用的模块,从而进一步提高智能合约开发的效率。 灵活性和安全性之间的谨慎平衡使 Sui Move 特别适合构建下一代区块链应用程序。
通过对这些概念的扎实掌握,你可以自信地探索更高级的主题,例如对象所有权和多版本并发控制。
我对 Sui Move 的探索才刚刚开始,但到目前为止所涵盖的基础知识已经揭示了该语言在构建可扩展、安全和高性能区块链解决方案方面的巨大潜力。
我们将在本系列文章的下一部分中再见。
The Move Book - https://move-book.com/index.html
Sui Docs - https://docs.sui.io/
- 原文链接: decipherclub.com/my-firs...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!