7.轻松入门Move: 对象(上)
有过后端编程经验的小伙伴会发现:无论什么语言核心其实都是对数据的增删改查,Move也不例外,但是Move并不会跟其他语言一样连接数据库、使用特定的数据库语言存储数据,而是使用对象作为最小的存储单元。也就是说如果你想持久化一些数据,先申明一个对象模型,使用要存储的数据实例化对象即可。如下:
#![allow(unused)] fn main() { //申明对象模型 public struct Article has key { id: object::UID, title: string::String, content: string::String, word_cnt: u64, } public fun new(title: vector<u8>, content: vector<u8>, ctx: &mut tx_context::TxContext) { let content = string::utf8(content); //实例化一个对象 let article = Article{ id: object::new(ctx), title: string::utf8(title), content: content, word_cnt: string::length(&content), }; //对象设置为共享对象,所有用户皆可访问修改 transfer::share_object<Article>(article); } }
在发布这段代码后,调用new方法就可以生成一个对象并返回对象的元数据,对象也保存在了链上。
对象模型是一种特殊的结构体,所以上一章讲到的结构体相关的用法对象也一样适用。那如何区分这是一个普通的结构体还是对象呢?对象一定具有key能力且对象的第一个字段是全局唯一ID ,这个全局唯一id可以确定对象在链上的位置,所以也可以理解为就是对象的地址。对象的其他字段则可以是基础数据类型、对象或者非对象结构体。
第一章我们也讲过,在发布代码的时候会返回一个所有者为Immutable(不可改变的)的对象,这个对象的ID就是对应的包地址。这句话隐含了一个信息,就是我们发布的包也是一个对象。这个对象永远不可改变或删除。所以发布代码的过程也可以理解为是把代码存储到区块链的过程。
对象的结构
使用sui client object
结构体对象:
包:
我们可以把对象分为两部分:元数据和内容。
公共元数据
以下是无论是对象模型还是包都有的公共元数据:
- 32个字节的全球唯一标识符(objectId),也就是对象ID,也可以说是对象的地址。
- 8个字节的版本号(version),每次修改对象都会加一。
- 32个字节的交易的摘要(prevTx),记录最后一次输出这个对象的交易。
- 33个字节的owner字段,对象的所有者,也可以根据这个字段推测如何访问这个对象。
- objectType则指明对象类型,值可能是package可能是自定义的结构体类型。
- digest字段是这个对象元数据和内容的哈希,也就是对象的摘要。
- storageRebate字段表示,如果这个对象后期在链上删除,将会返还的gas值。
对象内容
对象内容就一个content字段,它里面的dataType字段用于区分是结构体对象(moveObject)还是包(package)。content字段的其他内容则因类型的不同而各有区别了。
结构体对象
- type字段:表明结构体类型
- hasPublicTransfer字段:是否能使用publish_transfer转移所有权,上图这个对象因为是共享对象所以不能被转移所有权,值为false。
- fields字段就是对象的键值对,使用BCS(Binary Canonical Serialization)编码。我们可以在sui client object命令中指定--bcs选项来查看编码后的值。
包
Move Packages包含包中的代码。查看上图可以发现引用包的名称已经自动变成了包的地址。
删除对象
我们上面说对象就是一种特殊的结构体,按理说上一章的销毁实例应该也适用于对象吧?我们按照上一章讲解的方法定义drop函数:
#![allow(unused)] fn main() { public fun drop(a: Article) { let Article{id:_,title:_,content:_,word_cnt:_}=a; } }
编译的时候直接报错:id字段Cannot ignore values without the 'drop' ability. The value must be used。id字段的类型是sui::object::UID,这个结构体类型没有drop的能力,所以不能丢弃id字段值。好在sui::object模块提供了一个删除UID的方法,也是删除对象id的唯一方法,如下:
#![allow(unused)] fn main() { //注意:要删除对象,必须按值传入 public fun drop(a: Article) { let Article{id:id,title:_,content:_,word_cnt:_}=a; object::delete(id); } }
我们发布合约后调用这个方法删除对象。在浏览器查看本次交易的费用明细可以发现:本次交易给我们返还了0.000351964 SUI!删除对象释放存储空间就会返还一部分gas,那我们在编码过程中应该积极删除无用对象,以减少gas消耗!
对象的分类
根据对象的所有者和访问权限的不同,可以将对象分为以下几类:
-
独有对象
这种对象的owner字段值是账户的地址或者是对象的ID。它只能属于某一个地址,可以切换对象的所有者。也只有所有者能访问,修改,转交它。
-
共享对象
这种对象的owner字段值带有Shared标记。这种对象属于所有人,对所有人开放访问,修改的权限。
-
不可变对象
这种对象的owner字段值是Immutable,一旦创建不能修改和转交,但是对所有人开放访问权限。 我们每次发布包就会返回一个不可变对象,所有人都可以访问这个包但包一经发布不可修改。
- 被嵌套的对象
这种对象的owner字段值是嵌套对象的地址,。它被一个对象嵌套在内,在链上不能独立存在,也无法使用对象ID直接访问。只能通过嵌套他的对象访问,修改或转移。如果转移给了一个账户地址,该账户的用户就可以通过对象ID直接访问了。
本节我们只简单介绍一下每种对象的特征,下一节我们将详解讲解如何创建,访问和转交这些对象。
了解更多Move内容:
- telegram: t.me/move_cn
- QQ群: 79489587