Sui Move 中集合、对象、泛型、动态字段的使用。
在第一节入门 Sui Move 开发:1. 环境安装 中我们已经成功安装了 Sui Move
开发环境及开发 IDE。
在第二节入门 Sui Move 开发:2. 常用命令、编写并发布 Hello World 合约 中我们也了解了常用的命令并在链上发布了自己的第一个合约 hello world
。
在第三节 入门 Sui Move 开发:3. Sui Move 语法和设计模式 中我们了解学习 Sui Move 语法和设计模式,并发布了一个代币到链上。
本节内容其实是对上一节语法的补充。
vector
关键字定义存储多个同类型的变量(数组)。
let personList : vector<Person> = vector<Person>[];
let bools : vector<bool> = vector[false, false, true];
// 简单的例子,vector 提供的一些方法
let mut list: vector<u8> = vector[1, 3, 5];
// 添加一个值
vector::push_back(&mut list, 0);
// 删除最后一个值
vector::pop_back(&mut list);
// list 中数据的个数
vector::length(&list);
Table
是一个映射类的集合,可以动态存储键值对。
// 创建 table 类型变量
let mut table_values: Table<u8, u8> = table::new(ctx);
// 添加键值对
table::add(&mut table_values, 0, 10);
// 删除键值对
table::remove(&mut table_values, 0);
// 获取不可变引用 table 对象 key 对应的值
table::borrow(&table_values, 0);
// 获取可变引用 table 对象 key 对应的值
table::borrow_mut(&mut table_values, 0);
// table 中有 key 返回 true
table::contains(&table_values, 0);
// table 中键值对的个数
table::length(&table_values);
对象是一种特殊的结构体,对象具有key对象且第一个字段是全局唯一ID。
// 定义对象结构体
public struct Article has key {
id: UID,
name: String,
content: String,
}
开发中积极删除无用对象,减少 gas 消耗。
使用 object::delete
删除 id
。
public fun drop_article(a: Article) {
let Article{id: id, title:_, content: _, word_cnt: _} = a;
object::delete(id); // 删除对象 id 的唯一方法
}
对象分为:
对象的 owner
字段值是账户的地址或者是对象的 ID
。它只能属于 owner
,可以切换对象的 owner
,只有所有者能访问、修改,转移它。
创建一个对象后,使用 transfer::transfer
或 transfer::public_transfer
把所有权交给一个地址或对象 ID
,那么这个对象就是独有对象。
transfer
用于没有 store
能力的对象,只能在定义对象的模块中使用,public_transfer
用于有 store
能力的对象,可以在模块内外使用。
public fun new_share(title: vector<u8>, content: vector<u8>, ctx: &mut TxContext) {
// 创建 Article 实例
let article = Article {
id: object::new(ctx),
title: utf8(title),
content: contentStr,
};
// 将 article 变为独有对象
transfer::transfer(article, ctx.sender());
}
对象的 owner 字段值带有 Shared 标记,该对象属于所有人,对所有人开放访问,修改的权限。
创建一个对象后,使用 transfer::share_object
或 transfer::public_share_object
转移对象,那么对象就变成了共享对象。
对象的 owner 字段值是 Immutable,一旦创建不能修改和转交,但是对所有人开放访问权限。
每次发布包就会返回一个不可变对象,所有人都可以访问这个包但包一经发布不可修改。
创建一个不可变对象,使用 freeze_object
或 public_freeze_object
方法。过程不可逆。
对象可以被作为字段类型定义在另一个对象里。字段对象就是被嵌套的对象。
嵌套的方式:
直接嵌套
public struct Person has key, store {
id: UID,
name: String,
}
public struct Company has key {
id: UID,
// 直接嵌套:嵌套 Person 对象
person: Person,
can_be_transfered: bool,
}
// 访问直接嵌套的对象
public entry fun transfer_person(company: &Company, _: &mut TxContext) {
// 通过外层对象 company 来访问被嵌套对象 Person
let _ = company.person.name;
}
// 解除嵌套
public entry fun transfer_person_unwrapped(company: Company, ctx: &mut TxContext) {
let Company{
id: id,
person: person,
can_be_transfered: can_be_transfered,
} = company;
let _ = can_be_transfered;
// 转移 person 对象,person 就变成了独有对象
transfer::public_transfer(person, tx_context::sender(ctx));
// 删除外层对象
object::delete(id);
}
通过 Option 嵌套,适用于有一个对象嵌套或者没有嵌套的情况
// 通过 Option 嵌套
public struct PersonOption has key {
id: UID,
name: String,
notebook: Option<Notebook>,
}
public struct Notebook has key, store {
id: UID,
brand: String,
model: String,
}
public fun newPersonOption(ctx: &mut TxContext) {
transfer::transfer(PersonOption {
id: object::new(ctx),
name: utf8(b"hh"),
notebook: option::none<Notebook>(), // 实例化没有 Notebook 的 Person 对象
}, tx_context::sender(ctx));
}
// 嵌套 Notebook 对象
public fun fill_notebook(person: &mut PersonOption, ctx: &mut TxContext) {
// 首先判断之前没有嵌套 Notebook 对象
assert!(option::is_none(&person.notebook), 1);
let notebook = Notebook {
id: object::new(ctx),
brand: utf8(b"b_brand"),
model: utf8(b"m_model"),
};
// 嵌套 notebook
option::fill<Notebook>(&mut person.notebook, notebook);
}
// 访问被 option 嵌套的对象
public entry fun access_notebook(person: &PersonOption, _: &mut TxContext) {
let notebook_ref = option::borrow<Notebook>(&person.notebook);
_ = notebook_ref.brand;
}
// 解除被 option 嵌套的对象
public entry fun unwrap_notebook(person: &mut PersonOption, ctx: &mut TxContext) {
// 确认有嵌套
assert!(option::is_some(&person.notebook), 2);
// 解除嵌套并转给当前用户
let notebook = option::extract<Notebook>(&mut person.notebook);
transfer::public_transfer(notebook, tx_context::sender(ctx));
}
通过 vector 嵌套,适用于有零个或者多个对象嵌套的情况
// 通过 vector 嵌套
public struct PersonVector has key {
id: UID,
name: String,
notebooks: vector<Notebook>,
}
public struct Notebook has key, store {
id: UID,
brand: String,
model: String,
}
public fun newPersonVector(ctx: &mut TxContext) {
transfer::transfer(PersonVector{
id: object::new(ctx),
name: utf8(b"person_vector"),
notebooks: vector::empty<Notebook>(), // 实例化没有 notebook 的 PersonVector
}, tx_context::sender(ctx));
}
// 访问被 vecotr 嵌套的对象
public entry fun access_notebook_vector(person: &PersonVector, index: u64, _: &mut TxContext) {
let notebook_ref = vector::borrow(&person.notebooks, index);
_ = notebook_ref.brand;
}
// 解除被 vecotr 嵌套的对象
public entry fun unwrap_notebook_vector(person: &mut PersonVector, notebook: &Notebook, ctx: &mut TxContext) {
// 确认是否有嵌套
let (contains, index) = vector::index_of<Notebook>(&person.notebooks, notebook);
assert!(contains, 2);
// 解除嵌套,转给用户
let notebook = vector::remove<Notebook>(&mut person.notebooks, index);
transfer::public_transfer(notebook, tx_context::sender(ctx));
}
通过泛型实现只写一套逻辑,而应用于任何类型上。
定义一个可以装任意对象的箱子。
// 定义字段类型为泛型的结构体,T 就是泛型
public struct Box<T> {
value: T,
}
ability
可以使用 : store
来强制传递的泛型有特定 ability
。
// 强制传递的泛型有 store ability
public struct Box<T: store> has key, store {
id: UID,
value: T,
}
函数的参数也可以使用泛型,如下是 transfer::transfer()
的代码。传入的
public fun transfer<T: key>(obj: T, recipient: address) {
transfer_impl(obj, recipient)
}
当对象需要添加非固定类型的字段时,就用到了动态字段。
下面代码中详细介绍了如何创建、删除动态对象。
// 需要使用模块 dynamic_object_field
use sui::dynamic_object_field as ofield;
// 定义添加动态字段的结构体 PersonDynamic
public struct PersonDynamic has key {
id: UID,
name: String,
}
// 定义作为动态字段类型的结构体 Phone
public struct Phone has key, store {
id: UID,
pmodel: String,
}
// 定义作为动态字段类型的结构体 Computer
public struct Computer has key, store {
id: UID,
cname: String,
}
// 给对象添加动态字段
public entry fun add_obj(person: &mut PersonDynamic, ctx: &mut TxContext) {
// 创建 Phone 实例
let phone = Phone { id: object::new(ctx), pmodel: utf8(b"p_1") };
// person 中添加动态对象字段 Phone
ofield::add<String, Phone>(&mut person.id, phone.pmodel, phone);
// person 中添加动态对象字段 Computer
let computer = Computer { id: object::new(ctx), cname: utf8(b"c_1") };
ofield::add<String, Computer>(&mut person.id, computer.cname, computer);
}
// 删除对象的动态字段
public entry fun delete_obj(person: &mut PersonDynamic, buyer: address, _: &mut TxContext) {
// 字段不存在,就停止运行
assert!(ofield::exists_(&person.id, utf8(b"p_1")), 2);
let phone: Phone = ofield::remove(&mut person.id, utf8(b"p_1"));
// 转给其他人
transfer::public_transfer(phone, buyer);
}
// 删除有动态字段的对象
public entry fun delete_dynamic_obj(mut person: PersonDynamic, _: &mut TxContext) {
// 字段不存在,就停止运行
assert!(ofield::exists_(&person.id, utf8(b"c_1")), 0);
assert!(ofield::exists_(&person.id, utf8(b"b_1")), 1);
// 删除 phone 动态字段
let Phone{id: phoneId, pmodel: _} = ofield::remove(&mut person.id, utf8(b"c_1"));
object::delete(phoneId);
// 删除 computer
let Computer{id: computerId, cname: _} = ofield::remove(&mut person.id, utf8(b"c_1"));
object::delete(computerId);
// 删除 person 对象
let PersonDynamic{id: id, name: _} = person;
object::delete(id);
}
本节内容结束,本节中仍有很多需要记忆的地方,多写就会变得熟练。
如果觉得本节内容对你有所帮助,可以点赞鼓励下。
如果你对文章中内容有任何疑问可以留言。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!