4.MOVE从入门到实战-可编程Resource-如何使用Resource

  • 木头
  • 更新于 2022-08-10 15:03
  • 阅读 2925

Move 白皮书中详细描述了 Resource这个概念。最初,它是作为一种名为resource的结构体类型被实现,自从引入 ability 以后,它被实现成拥有 KeyStore 两种 ability 的结构体。Resource`可以安全的表示数字资产,它不能被复制,也不能被丢弃或重新使用,但是它却可以被安全地存储和转移。

定义

Resource 是一种用 keystore ability 限制了的结构体:

module M {
    struct T has key, store {
        field: u64
    }
}

Resource 的限制

在代码中,Resource 类型有几个主要限制:

  1. Resource 存储在帐户下。因此,只有在分配帐户后才会存在,并且只能通过该帐户访问。
  2. 一个帐户同一时刻只能容纳一个某类型的 Resource。
  3. Resource 不能被复制;与它对应的是一种特殊的kindresource,它与copyable不同。
  4. Resource 必需被使用,这意味着必须将新创建的 Resource move到某个帐户下,从帐户移出的Resource 必须被解构或存储在另一个帐户下。

创建和移动 Resource

首先,让我们创建模块:

// sources/Collection.move
module std::Collection {

    struct Item has store {
        // we'll think of the properties later
    }

    struct Collection has key {
        items: vector<Item>
    }
}

一个模块里最主要的 Resource 通常跟模块取相同的名称(例如这里的 Collection)。遵循这个惯例,你的模块将易于阅读和使用。

创建和移动

我们定义了一个 Resource 结构体T,该结构体将保存一个向量,向量里面存放Item类型的元素。现在,让我们看看如何创建新集合以及如何在 account 下存储 Resource。Resource 将永久保存在发送者的地址下,没有人可以从所有者那里修改或取走此 Resource。


// sources/Collection.move
module std::Collection {
use std::vector;
struct Item has store {}

struct Collection has key {
    items: vector<Item>
}

/// note that &signer type is passed here!
public fun start_collection(account: &signer) {
    move_to<Collection>(account, Collection {
        items: vector::empty<Item>()
    })
}

}

还记得 `signer` 吗?现在,你将了解它的运作方式!移动 Resource 到 account 需要使用内建函数 move_to,需要 `signer` 作为第一个参数,T 作为第二个参数。move_to 函数的签名可以表示为:
```js
native fun move_to<T: key>(account: &signer, value: T);

总结一下上面所学的内容:

  1. 你只能将 Resource 放在自己的帐户下。你无权访问另一个帐户的 signer 值,因此无法放置 Resource 到其它账户。
  2. 一个地址下最多只能存储一个同一类型的 Resource。两次执行相同的操作是不行的,比如第二次尝试创建已有 Resource 将会导致失败。

查看 Resource 是否存在

Move 提供 exists函数来查看某 Resource 是否存在于给定地址下,函数签名如下:

native fun exists<T: key>(addr: address): bool;

通过使用泛型,此函数成为独立于类型的函数,你可以使用任何 Resource 类型来检查其是否存在于给定地址下。实际上,任何人都可以检查给定地址处是否存在 Resource。但是检查是否存在并不意味着能获取储 Resource !

让我们编写一个函数来检查用户是否已经拥有 resource T:

// sources/Collection.move
module std::Collection {
    use std::vector;

    struct Item has store,drop {}

    struct Collection has key {
        items: vector<Item>
    }

    // note that &signer type is passed here!
    public fun start_collection(account: &signer) {
        move_to<Collection>(account, Collection {
            items: vector::empty<Item>()
        })
    }
    // this function will check if resource exists at address
    public fun exists_at(at: address): bool {
        exists<Collection>(at)
    }
}

现在你已经知道了如何创建 Resource,如何将其移动到发送者账户下以及如何检查 Resource 是否已经存在,现在是时候学习如何访问和修改 Resource 了。

读取和修改 Resource

Move 有两个内建函数用来读取和修改 Resource。它们的功能就像名字一样:borrow_global 和 borrow_global_mut。

不可变借用 borrow_global

可变引用(&mut)和不可变的引用(&)

// sources/Collection.move
module std::Collection {
    use std::vector;
    use std::signer;

    struct Item has store,drop {}

    struct Collection has key {
        items: vector<Item>
    }

    // note that &signer type is passed here!
    public fun start_collection(account: &signer) {
        move_to<Collection>(account, Collection {
            items: vector::empty<Item>()
        })
    }

    // this function will check if resource exists at address
    public fun exists_at(at: address): bool {
        exists<Collection>(at)
    }

    // get collection size
    // mind keyword acquires!
    public fun size(account: &signer): u64 acquires Collection {
        let owner = signer::address_of(account);
        let collection = borrow_global<Collection>(owner);

        vector::length(&collection.items)
    }
}

这里发生了很多事情。首先,让我们看一下函数的签名。全局函数 borrow_global 返回了对 Resource T 的不可变引用。其签名如下:

native fun borrow_global<T: key>(addr: address): &T;

通过使用此功能,我们可以读取存储在特定地址的 Resource。这意味着该模块(如果实现了此功能)具有读取任何地址上任何 Resource 的能力,当然这里的 Resource 指的是该模块内定义的任何 Resource。

另一个结论:由于 Borrow检查,你不能返回对 Resource 的引用或对其内容的引用(因为对 Resource 的引用将在函数作用域结束时消失)。

由于 Resource 是不可复制的类型,因此不能在其上使用取值运算符 “*”。

Acquires 关键字

还有另一个值得解释的细节:关键字 acquires。该关键字放在函数返回值之后,用来显式定义此函数获取的所有 Resource。我们必须指定所有获取的 Resource,即使它实际上是子函数所获取的 Resource,即父函数必须在其获取列表中包含子函数的获取列表。

acquires 使用方法如下:

fun <name>(<args...>): <ret_type> acquires T, T1 ... {

可变借用 borrow_global_mut

要获得对 Resource 的可变引用,请添加_mutborrow_global 后,仅此而已。让我们添加一个函数,将新的 Item 添加到集合中。

// sources/Collection.move
module std::Collection {

    // ... skipped ...

    // add item to collection
    public fun add_item(account: &signer) acquires Collection {
        let collection = borrow_global_mut<Collection>(signer::address_of(account));
        vector::push_back(&mut collection.items, Item {});
    }
}

对 Resource 的可变引用允许创建对其内容的可变引用。这就是为什么我们可以在此示例中修改内部向量 items 的原因。

native fun borrow_global_mut<T: key>(addr: address): &mut T;

使用和销毁

取出和销毁 Resource

move_from,它用来将 Resource 从账户下取出。我们将实现 destroy 函数,将Collection 的 T Resource 从账户取出并且销毁它的内容。

// sources/Collection.move
module std::Collection {

    // ... skipped ...

    // remove item from collection
    public fun destroy(account: &signer) acquires Collection {

        // account no longer has resource attached
        let collection = move_from<Collection>(signer::address_of(account));

        // now we must use resource value - we'll destructure it
        // look carefully - Items must have drop ability
        let Collection { items: _ } = collection;

        // done. resource destroyed
    }
}

Resource 必需被使用。因此,从账户下取出 Resource 时,要么将其作为返回值传递,要么将其销毁。但是请记住,即使你将此 Resource 传递到外部并在脚本中获取,接下来能做的操作也非常有限。因为脚本上下文不允许你对结构体或 Resource 做任何事情,除非 Resource 模块中定义了操作 Resource 公开方法,否则只能将其传递到其它地方。知道这一点,就要求我们在设计模块时,为用户提供操作 Resource 的函数。

move_from 函数签名:

native fun move_from<T: key>(addr: address): T;
点赞 4
收藏 2
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

1 条评论

请先 登录 后评论
木头
木头
0xC020...10cf
江湖只有他的大名,没有他的介绍。