对象数据结构 - 一袋混合对象

在上一节课中,我们介绍了如何将 SuiFren 对象包装到 GiftBox 对象中。

public struct GiftBox has key {
    id: UID,
    inner: SuiFren,
}

entry fun wrap_fren(fren: SuiFren, ctx: &mut TxContext) {
    let gift_box = GiftBox {
        id: object::new(ctx),
        inner: fren,
    };
    transfer::transfer(gift_box, tx_context::sender(ctx));
}

假设我们希望让 GiftBox 变得更加令人惊喜,允许打开它的用户可能会收到 1 到 5 个 Frens。你可能会倾向于创建 5 个 SuiFren 字段:

public struct GiftBox has key {
    id: UID,
    inner_1: SuiFren,
    inner_2: SuiFren,
    inner_3: SuiFren,
    inner_4: SuiFren,
    inner_5: SuiFren,
}

这看起来很乱,而且实际上并不有效!这将迫使 GiftBox 总是包含恰好 5 个 SuiFren。 如果我们还允许 GiftBox 为空,可能不包含任何 SuiFren,该怎么办?更好的方法是使用向量:

public struct GiftBox has key {
    id: UID,
    frens: vector<SuiFren>,
}

这简洁多了!现在每个 GiftBox 可以包含任意数量的 SuiFren,包括零(空向量)。 但如果我们想让它更有趣一些——每个 GiftBox 也可以包含一些帽子呢?我们可以创建第二个帽子向量 。 但你看到了问题所在。如果我们希望 GiftBox 包含不同类型对象的混合集合, 向量在这里不起作用,因为它只能包含单一类型的对象。更好的方法是使用对象袋(Object Bag)!

use sui::object_bag::{Self, ObjectBag};

public struct MyBag has key {
    id: UID,
    object_bag: ObjectBag,
}

public fun create_bag(ctx: &mut TxContext) {
    transfer::transfer(MyBag {
        id: object::new(ctx),
        object_bag: object_bag::new(ctx),
    }, tx_context::sender(ctx));
}

public fun add_to_bag<SomeObject>(my_bag: &mut MyBag, key: String, object: SomeObject) {
    object_bag::add(&mut my_bag.object_bag, key, object);
}

在底层,对象袋(ObjectBag)使用动态对象字段来存储不同类型的对象。 语法与动态字段非常相似——要向袋子中添加对象,你需要一个原始类型的键(例如 u64 类型的索引)或具有 copy/drop/store 能力的结构体, 以及一个具有 key 能力(对象结构体)的结构体类型值。

object_bag 模块还提供其他函数,例如 borrowborrow_mutcontains(类似于 dynamic_field::exists_)、 length(返回袋子中的对象数量)和 is_empty