Skip to content

动态集合

在 集合 部分,我们学习了 Sui 框架提供的基于向量的集合类型。基于向量的集合对于存储小型数据集非常有用,但它们有两个主要限制:

  • 受对象大小的限制:无法存储超大数据量。
  • 强类型:只能存储单一类型的项目。

为了解决这些限制,Sui 引入了 动态集合 的概念。动态集合建立在 动态字段 之上,支持存储任意数量的任何类型项目,不受对象大小的限制。

动态字段

动态字段是 Sui 中的一种灵活机制,允许将任意数据附加到对象上。它们类似键值存储,其中键为 vector<u8> 类型,值可以是任意类型。

动态字段的两种形式

  1. 动态字段:用于将任意数据附加到对象。
  2. 动态对象字段:专门用于将对象附加到其他对象。

使用动态字段

要使用动态字段,需引入 sui::dynamic_field 模块。以下是一些常用方法:

  • add:向对象添加动态字段。
  • remove:从对象中移除动态字段。
  • borrow:借用动态字段。
  • borrow_mut:借用动态字段的可变引用。
  • contains:检查字段是否存在。

示例:动态字段的用法

以下示例展示如何向 角色 对象附加动态字段:

move
module book::dynamic_fields;

use sui::dynamic_field as df;
use std::string::String;

/// 定义角色对象
public struct 角色 has key {
    id: UID,
}

/// 定义两个可附加的配件对象
public struct 帽子 has key, store {
    id: UID,
    颜色: u32,
}

public struct 胡子 has key, store {
    id: UID,
}

#[test]
fun 测试动态字段() {
    let ctx = &mut tx_context::dummy();
    let mut 角色 = 角色 { id: object::new(ctx) };

    // 向角色附加一个帽子
    df::add(
        &mut 角色.id,
        b"帽子",
        帽子 { id: object::new(ctx), 颜色: 0xFF0000 },
    );

    // 附加胡子
    df::add(
        &mut 角色.id,
        b"胡子",
        胡子 { id: object::new(ctx) },
    );

    // 检查是否成功附加
    assert!(df::contains(&角色.id, b"帽子"), 0);
    assert!(df::contains(&角色.id, b"胡子"), 1);

    // 修改附加字段
    let 帽子: &mut 帽子 = df::borrow_mut(&mut 角色.id, b"帽子");
    帽子.颜色 = 0x00FF00;

    // 移除附加字段
    let 帽子: 帽子 = df::remove(&mut 角色.id, b"帽子");
    let 胡子: 胡子 = df::remove(&mut 角色.id, b"胡子");

    // 确认字段已移除
    assert!(!df::contains(&角色.id, b"帽子"), 0);
    assert!(!df::contains(&角色.id, b"胡子"), 1);

    sui::test_utils::destroy(角色);
    sui::test_utils::destroy(帽子);
    sui::test_utils::destroy(胡子);
}

通过动态字段,我们可以附加不同类型的对象到同一个主体对象上。

动态对象字段

动态对象字段 是动态字段的特化版本,仅用于附加具有 key 能力的对象。

示例:动态对象字段

以下是动态对象字段的基本用法:

move
module book::dynamic_object_field;

use sui::dynamic_object_field as dof;
use sui::dynamic_field as df;
use std::string::String;

/// 定义角色和附加对象
public struct 角色 has key {
    id: UID,
}

public struct 配件 has key, store {
    id: UID,
}

public struct 元数据 has store, drop {
    名称: String,
}

#[test]
fun 测试动态对象字段() {
    let ctx = &mut tx_context::dummy();
    let mut 角色 = 角色 { id: object::new(ctx) };

    // 创建并附加配件
    let 帽子 = 配件 { id: object::new(ctx) };
    dof::add(&mut 角色.id, b"帽子", 帽子);

    // 附加非对象类型字段
    df::add(&mut 角色.id, b"元数据", 元数据 { 名称: b"角色名".to_string() });

    // 借用和移除动态字段
    let 帽子_ref: &配件 = dof::borrow(&角色.id, b"帽子");
    let 帽子: 配件 = dof::remove(&mut 角色.id, b"帽子");

    sui::test_utils::destroy(帽子);
    sui::test_utils::destroy(角色);
}

动态集合类型

Sui 提供了三种动态集合类型:

  1. 包(Bag) 存储任意类型数据,定义于 sui::bag 模块。
  2. 对象包(Object Bag) 仅存储具有 key 能力的对象,定义于 sui::object_bag 模块。
  3. 表(Table) 支持键值对存储,定义于 sui::table 模块。

示例:使用 Table

以下示例展示如何使用 Table 存储地址及记录:

move
use sui::table::{Self, Table};

public struct 用户注册表 has key {
    id: UID,
    表: Table<address, String>,
}

#[test]
fun 测试表() {
    let ctx = &mut tx_context::dummy();

    let mut 表 = table::new<address, String>(ctx);
    assert!(.length() == 0, 0);

.add(@0xa11ce, b"记录1".to_string());
.add(@0xb0b, b"记录2".to_string());

    assert!(.length() == 2, 2);

    let 记录 = .remove(@0xa11ce);
.destroy_empty();
}

总结

  • 动态集合是基于动态字段的集合,灵活性极高。
  • 不受对象大小限制,支持存储任意类型数据。
  • 支持多种集合类型:包、对象包和表,满足不同场景需求。