You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
module ds::v_map{
use sui::vec_map;use std::vector;
public entry fun example(){let m = vec_map::empty();let i = 0;while(i < 10){let k = i + 2;let v = i + 5;
vec_map::insert(&mut m, k, v);
i = i + 1;};assert!(!vec_map::is_empty(&m),0);assert!(vec_map::size(&m) == 10,1);let i = 0;// make sure the elements are as expected in all of the getter APIs we exposewhile(i < 10){let k = i + 2;assert!(vec_map::contains(&m,&k),2);let v = *vec_map::get(&m,&k);assert!(v == i + 5,3);assert!(vec_map::get_idx(&m,&k) == i,4);let(other_k, other_v) = vec_map::get_entry_by_idx(&m, i);assert!(*other_k == k,5);assert!(*other_v == v,6);
i = i + 1;};// 移出所有元素let(keys, values) = vec_map::into_keys_values(copy m);let i = 0;while(i < 10){let k = i + 2;let(other_k, v) = vec_map::remove(&mut m,&k);assert!(k == other_k,7);assert!(v == i + 5,8);assert!(*vector::borrow(&keys, i) == k,9);assert!(*vector::borrow(&values, i) == v,10);
i = i + 1;}}}
module ds::v_set{
use sui::vec_set;use std::vector;
public entry fun example(){let m = vec_set::empty();let i = 0;while(i < 10){let k = i + 2;
vec_set::insert(&mut m, k);
i = i + 1;};assert!(!vec_set::is_empty(&m),0);assert!(vec_set::size(&m) == 10,1);let i = 0;// make sure the elements are as expected in all of the getter APIs we exposewhile(i < 10){let k = i + 2;assert!(vec_set::contains(&m,&k),2);
i = i + 1;};// 移出所有元素let keys = vec_set::into_keys(copy m);let i = 0;while(i < 10){let k = i + 2;
vec_set::remove(&mut m,&k);assert!(*vector::borrow(&keys, i) == k,9);
i = i + 1;}}}
module ds::structs{// 二维平面点
struct Point has copy, drop, store {x: u64,y: u64,}// 圆structCircle has copy, drop, store {center:Point,radius:u64,}// 创建结构体
public fun new_point(x: u64, y: u64):Point{Point{
x, y
}}// 访问结构体数据
public fun point_x(p:&Point):u64{
p.x}
public fun point_y(p:&Point):u64{
p.y}fun abs_sub(a: u64, b: u64):u64{if(a < b){
b - a
}else{
a - b
}}// 计算点之间的距离
public fun dist_squared(p1:&Point, p2:&Point):u64{let dx = abs_sub(p1.x, p2.x);let dy = abs_sub(p1.y, p2.y);
dx * dx + dy * dy
}
public fun new_circle(center:Point, radius:u64):Circle{Circle{ center, radius }}// 计算两个圆之间是否相交
public fun overlaps(c1:&Circle, c2:&Circle):bool{let d = dist_squared(&c1.center,&c2.center);let r1 = c1.radius;let r2 = c2.radius;
d * d <= r1 * r1 + 2* r1 * r2 + r2 * r2
}}
对象(Object)
对象是 Sui Move 中新引入的概念,也是 Sui 安全和高并发等众多特性的基础。定义一个对象,需要为结构体添加 key 能力,同时结构体的第一个字段必须是 UID 类型的 id。
对象虽然可以进行包装,但是也有一些局限,一是对象中的字段是有限的,在结构体定义是已经确定;二是包含其他对象的对象可能非常大,可能会导致交易 gas 很高,Sui 默认结构体大小限制为 2MB;再者,当遇到要储存不一样类型的对象集合时,问题就会比较棘手,Move 中的 vector 只能存储相同的类型的数据。
因此,Sui 提供了 dynamic field,可以使用任意名字做字段,也可以动态添加和删除。唯一影响的是 gas 的消耗。
dynamic field 包含两种类型,field 和 Object field,区别在于,field 可以存储任何有 store 能力的值,但是如果是对象的话,对象会被认为是被包装而不能通过 ID 被外部工具(浏览器,钱包等)访问;而 Object field 的值必须是对象(有 key 能力且第一个字段是 id: UID),对象仍然能从外部工具通过 ID 访问。
dynamic filed 的名称可以是任何拥有 copy,drop 和 store 能力的值,这些值包括 Move 中的基本类型(整数,布尔值,字节串),以及拥有 copy,drop 和 store 能力的结构体。
下面我们通过例子来看看具体的操作:
添加字段: add
访问和修改字段: borrow, borow_mut
删除字段
module ds::fields{
use sui::object::{Self,UID};use sui::dynamic_object_field as dof;use sui::transfer;use sui::tx_context::{Self,TxContext};structParent has key {id:UID,}structChild has key, store {id:UID,count:u64,}
public entry fun initialize(ctx:&mut TxContext){
transfer::transfer(Parent{id: object::new(ctx)}, tx_context::sender(ctx));
transfer::transfer(Child{id: object::new(ctx),count:0}, tx_context::sender(ctx));}
public entry fun add_child(parent:&mut Parent, child:Child){
dof::add(&mut parent.id,b"child", child);}
public entry fun mutate_child(child:&mut Child){
child.count = child.count + 1;}
public entry fun mutate_child_via_parent(parent:&mut Parent){mutate_child(dof::borrow_mut<vector<u8>,Child>(&mut parent.id,b"child",));}
public entry fun delete_child(parent:&mut Parent){letChild{ id,count: _ } = dof::remove<vector<u8>,Child>(&mut parent.id,b"child",);
object::delete(id);}
public entry fun reclaim_child(parent:&mut Parent, ctx:&mut TxContext){let child = dof::remove<vector<u8>,Child>(&mut parent.id,b"child",);
transfer::transfer(child, tx_context::sender(ctx));}}
前面介绍过,带有 dynamic field 的对象可以被删除,但是这对于链上集合类型来说这是不希望发生的,因为链上集合类型可能将无限多的键值对作为 dynamic field 保存。因此,在 Sui 提供了两种集合类型: Table 和 Bag,两者都基于 dynamic field 构建的映射类型的数据结构,但是额外支持计算它们包含的条目数,并防止在非空时意外删除。
Table 和 Bag 的区别在于,Table 是同质(*homogeneous)*映射,所以的键必须是同一个类型,所以的值也必须是同一个类型,而 Bag 是异质(heterogeneous)映射,可以存储任意类型的键值对。
同时,Sui 标准库中还包含对象版本的 Table 和 Bag: ObjectTable 和 ObjectBag,区别在于前者可以将任何 store 能力的值保存,但从外部存储查看时,作为值存储的对象将被隐藏,后者只能将对象作为值存储,但可以从外部存储中通过 ID 访问这些对象。
与之前介绍过的 vec_map 相比,table 更适合用来处理包含大量映射的情况。
Table
下面我们通过示例来展示对 table 的基本操作:
添加元素: add
读取和修改元素: borrow,borrow_mut
删除元素: delete
元素长度: length
判断存在性:contains
Object table 的操作与 table 类似。
module ds::tables{
use sui::object::{Self,UID};use sui::transfer;use sui::tx_context::{Self,TxContext};use sui::table::{Self,Table};constEChildAlreadyExists:u64 = 0;constEChildNotExists:u64 = 1;structParent has key {id:UID,children:Table<u64,Child>,}structChild has key, store {id:UID,age:u64}// 创建 Parent 对象
public entry fun initialize(ctx:&mut TxContext){
transfer::transfer(Parent{id: object::new(ctx),children: table::new(ctx)},
tx_context::sender(ctx));}
public fun child_age(child:&Child):u64{
child.age}// 查看
public fun child_age_via_parent(parent:&Parent, index:u64):u64{assert!(!table::contains(&parent.children, index),EChildNotExists);
table::borrow(&parent.children, index).age}// 获取长度
public fun child_size_via_parent(parent:&Parent):u64{
table::length(&parent.children)}// 添加
public entry fun add_child(parent:&mut Parent, index:u64, ctx:&mut TxContext){assert!(table::contains(&parent.children, index),EChildAlreadyExists);
table::add(&mut parent.children, index,Child{id: object::new(ctx),value:0});}// 修改
public fun mutate_child(child:&mut Child){
child.age = child.age + 1;}
public entry fun mutate_child_via_parent(parent:&mut Parent, index:u64){mutate_child(table::borrow_mut(&mut parent.children, index));}// 删除
public entry fun delete_child(parent:&mut Parent, index:u64){assert!(!table::contains(&parent.children, index),EChildNotExists);letChild{ id,age: _ } = table::remove(&mut parent.children,
index
);
object::delete(id);}}
Bag
Bag 的操作与 table 的操作接口类似:
添加元素: add
读取和修改元素: borrow,borrow_mut
删除元素: delete
元素长度: length
判断存在性:contains
这里我们仅展示添加不同类型的键值对。
Object_bag 的操作与 bag 类似。
module ds::bags{
use sui::object::{Self,UID};use sui::transfer;use sui::tx_context::{Self,TxContext};use sui::bag::{Self,Bag};constEChildAlreadyExists:u64 = 0;constEChildNotExists:u64 = 1;structParent has key {id:UID,children:Bag,}structChild1 has key, store {id:UID,value:u64}structChild2 has key, store {id:UID,value:u64}
public entry fun initialize(ctx:&mut TxContext){
transfer::transfer(Parent{id: object::new(ctx),children: bag::new(ctx)},
tx_context::sender(ctx));}// 添加第一种类型
public entry fun add_child1(parent:&mut Parent, index:u64, ctx:&mut TxContext){assert!(bag::contains(&parent.children, index),EChildAlreadyExists);
bag::add(&mut parent.children, index,Child1{id: object::new(ctx),value:0});}// 添加第二种类型
public entry fun add_child2(parent:&mut Parent, index:u64, ctx:&mut TxContext){assert!(bag::contains(&parent.children, index),EChildAlreadyExists);
bag::add(&mut parent.children, index,Child2{id: object::new(ctx),value:0});}}
LinkedTable
linked_table 是另一种使用 dynamic field 实现的数据结构,它与 table 类似,除此之外,它还支持值的有序插入和删除。因此,除了 table 类似的基础操作方法,还包含 front,back,push_front,push_back,pop_front,pop_back等操作,对于每一个键,也可以通过 prev 和 next 获取前一个和后一个插入的键。
这篇文章中,我们将介绍 Sui 中常见的数据结构,这些结构包含 Sui Move 和 Sui Framework 中提供的基础类型和数据结构,理解和熟悉这些数据结构对于 Sui Move 的理解和应用大有裨益。
首先,我们先快速复习一下 Sui Move 中使用到的基础类型。
无符号整型(Integer)
Move 包含六种无符号整型:
u8
,u16
u32
,u64
,u128
和u256
。值的范围从 0 到 与类型大小相关的最大值。这些类型的字面值为数字序列(例如 112)或十六进制文字,例如
0xFF
。 字面值的类型可以选择添加为后缀,例如112u8
。 如果未指定类型,编译器将尝试从使用文字的上下文中推断类型。 如果无法推断类型,则假定为u64
。对无符号整型支持的运算包括:
+
-
*
%
/
&
|
^
>>
<<
>
<
>=
<=
==
!=
as
简单示例:
布尔类型(Bool)
Move 布尔值包含两种,
true
和false
。支持与&&
,或||
和非!
运算。可以用于 Move 的控制流和assert!
中。assert!
是 Move 提供的用于断言,当判断的值是false
时,程序会抛出错误并停止。地址(Address)
address 也是 Move 的原生类型,可以在地址下保存模块和资源。Sui 中地址的长度为 20 字节。
在表达式中,地址需要使用前缀
@
,例如:Tuples 和 Unit
Tuples 和 Unit
()
在 Move 中主要用作函数返回值。只支持解构(destructuring)运算。接下来,我们从 Vector 开始,介绍 Sui 和 Sui Framework 中支持的集合类型。
数组(Vector)
vector<T>
是 Move 提供的唯一的原生集合类型。vector<T>
是由一组相同类型的值组成的数组,比如vector<u64>
,vector<address>
等。vector
支持的主要操作有:push_back
pop_back
borrow
,borrow_mut
contains
swap
index_of
编译并运行示例:
下面我们介绍几种基于
vector
的数据类型。字符串(String)
Move 没有字符串的原生类型,但它使用
vector<u8>
表示字节数组。目前,vector<u8>
字面量有两种:字节字符串(byte strings)和十六进制字符串(hex strings)。字节字符串是以
b
为前缀的字符串文字,例如b"Hello!\n"
。十六进制字符串是以
x
为前缀的字符串文字,例如x"48656C6C6F210A"
。每一对字节的范围从00
到FF
,表示一个十六进制的u8
。因此我们可以知道:b"Hello" == x"48656C6C6F"
。在
vector<u8>
的基础上,Move 提供了string
包处理 UTF8 字符串的操作。我们以创建 Name NFT 的为例:
编译后命令行中调用:
可以在 Transaction Effects 中看到新创建的对象,ID 为
0xf53891c8d200125bcfdba69557b158395bdf9390
,通过 Sui 提供的 RPC-API 接口sui_getObject
可以看到其中保存的内容:输出结果
VecMap 和 VecSet
Sui 在
vector
的基础上实现了两种数据结构,映射vec_map
和集合vec_set
。vec_map
是一种映射结构,保证不包含重复的键,但是条目按照插入顺序排列,而不是按键的顺序。所有的操作时间复杂度为0(N)
,N 为映射的大小。vec_map
只是为了提供方便的操作映射的接口,如果需要保存大型的映射,或者是需要按键的顺序排序的映射都需要另外处理。可以考虑使用之后介绍的table
数据结构。主要操作包括:
empty
insert
get
,get_mut
remove
contains
size
into_keys_values
keys
destroy_empty
get_entry_by_idx
,get_entry_by_idx_mut
vec_set
结构保证其中不包含重复的键。所有的操作时间复杂度为O(N)
,N 为映射的大小。同样,vec_set
提供了方便的集合操作接口,按插入顺序进行排序,如果需要使用按键进行排序的集合,也需要另外处理。主要操作包括:
empty
insert
remove
contains
size
into_keys
优先队列(PriorityQueue)
还有一种基于
vector
构建的数据结构:优先队列,他使用基于vector
实现的大顶堆(max heap)来实现。大顶堆是一种二叉树结构,每个节点的值都大于或等于其左右孩子节点的值,这样,这个二叉树的根节点始终都是所有节点中值最大的节点。
在优先队列中,我们为每一个节点赋予一个权重,我们基于权重构建一个大顶堆,从大顶堆顶部弹出根节点则为权重最大的节点。这样就形成过了一个按优先级弹出的队列。
优先队列主要包含的操作为:
create_entries
,结果作为new
方法参数new
insert
pop_max
示例:
结构体(Struct)
Move语言中,结构体是包含类型化字段的用户定义数据结构。 结构可以存储任何非引用类型,包括其他结构。示例:
对象(Object)
对象是 Sui Move 中新引入的概念,也是 Sui 安全和高并发等众多特性的基础。定义一个对象,需要为结构体添加
key
能力,同时结构体的第一个字段必须是UID
类型的 id。对象结构中除了可以使用基础数据结构外,也可以包含另一个对象,即对象可以进行包装,在一个对象中使用另一个对象。
对象有不同的所有权形式,可以存放在一个地址下面,也可以设置成不可变对象或者全局对象。不可变对象永远不能被修改,转移或者删除,因此它不属于任何人,但也可以被任何人访问。比如合约包对象,Coin Metadata 对象。
我们可以通过
transfer
包中的方法对对象进行处理:transfer
:将对象放到某个地址下freeze_object
:创建不可变对象share_object
:创建共享对象编译后调用:
可以看到,不同所有权类型的对象会在创建时显示不同的类型结果。
可以在结果中看到
Mutated Objects
中对象已经发生了变化。Dynamic field 和 Dynamic object field
对象虽然可以进行包装,但是也有一些局限,一是对象中的字段是有限的,在结构体定义是已经确定;二是包含其他对象的对象可能非常大,可能会导致交易 gas 很高,Sui 默认结构体大小限制为 2MB;再者,当遇到要储存不一样类型的对象集合时,问题就会比较棘手,Move 中的
vector
只能存储相同的类型的数据。因此,Sui 提供了 dynamic field,可以使用任意名字做字段,也可以动态添加和删除。唯一影响的是 gas 的消耗。
dynamic field 包含两种类型,field 和 Object field,区别在于,field 可以存储任何有
store
能力的值,但是如果是对象的话,对象会被认为是被包装而不能通过 ID 被外部工具(浏览器,钱包等)访问;而 Object field 的值必须是对象(有key
能力且第一个字段是id: UID
),对象仍然能从外部工具通过 ID 访问。dynamic filed 的名称可以是任何拥有
copy
,drop
和store
能力的值,这些值包括 Move 中的基本类型(整数,布尔值,字节串),以及拥有copy
,drop
和store
能力的结构体。下面我们通过例子来看看具体的操作:
add
borrow
,borow_mut
编译并调用
initialize
和add_child
方法:可以通过
sui_getDynamicFields
方法查看添加的字段:结果:
其中
name
为“child”
。同时,对于对象 ID0x55536ca8123ffb606398da9f7d2472888ca5bfd1
,我们仍然能从链上追踪对应信息。集合数据类型
接下来,我们介绍几种基于 dynamic field 的集合数据类型。
前面介绍过,带有 dynamic field 的对象可以被删除,但是这对于链上集合类型来说这是不希望发生的,因为链上集合类型可能将无限多的键值对作为 dynamic field 保存。因此,在 Sui 提供了两种集合类型:
Table
和Bag
,两者都基于 dynamic field 构建的映射类型的数据结构,但是额外支持计算它们包含的条目数,并防止在非空时意外删除。Table
和Bag
的区别在于,Table 是同质(*homogeneous)*映射,所以的键必须是同一个类型,所以的值也必须是同一个类型,而 Bag 是异质(heterogeneous)映射,可以存储任意类型的键值对。同时,Sui 标准库中还包含对象版本的
Table
和Bag
:ObjectTable
和ObjectBag
,区别在于前者可以将任何store
能力的值保存,但从外部存储查看时,作为值存储的对象将被隐藏,后者只能将对象作为值存储,但可以从外部存储中通过 ID 访问这些对象。与之前介绍过的
vec_map
相比,table
更适合用来处理包含大量映射的情况。Table
下面我们通过示例来展示对 table 的基本操作:
add
borrow
,borrow_mut
delete
length
contains
Object table 的操作与 table 类似。
Bag
Bag 的操作与 table 的操作接口类似:
add
borrow
,borrow_mut
delete
length
contains
这里我们仅展示添加不同类型的键值对。
Object_bag
的操作与bag
类似。LinkedTable
linked_table
是另一种使用 dynamic field 实现的数据结构,它与table
类似,除此之外,它还支持值的有序插入和删除。因此,除了 table 类似的基础操作方法,还包含front
,back
,push_front
,push_back
,pop_front
,pop_back
等操作,对于每一个键,也可以通过prev
和next
获取前一个和后一个插入的键。TableVec
最后,我们介绍一种基于
table
的数据结构table_vec
。从名字就可以看出,table_vec
是使用table
实现的可扩展vector
,它使用元素在vector
的索引作为table
中的键进行存储。table_vec
提供了与vector
类似的操作方法。编译并运行示例:
sui client call \ --function example \ --module table_vecs \ --package ${package_id} \ --gas-budget 30000
至此,我们介绍完了 Sui Move 中主要的数据类型及其使用方法,希望大家学习和理解 Sui Move 有一定的帮助。
The text was updated successfully, but these errors were encountered: