动态集合
在 集合 部分,我们学习了 Sui 框架提供的基于向量的集合类型。基于向量的集合对于存储小型数据集非常有用,但它们有两个主要限制:
- 受对象大小的限制:无法存储超大数据量。
- 强类型:只能存储单一类型的项目。
为了解决这些限制,Sui 引入了 动态集合 的概念。动态集合建立在 动态字段 之上,支持存储任意数量的任何类型项目,不受对象大小的限制。
动态字段
动态字段是 Sui 中的一种灵活机制,允许将任意数据附加到对象上。它们类似键值存储,其中键为 vector<u8>
类型,值可以是任意类型。
动态字段的两种形式
- 动态字段:用于将任意数据附加到对象。
- 动态对象字段:专门用于将对象附加到其他对象。
使用动态字段
要使用动态字段,需引入 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 提供了三种动态集合类型:
- 包(Bag) 存储任意类型数据,定义于
sui::bag
模块。 - 对象包(Object Bag) 仅存储具有
key
能力的对象,定义于sui::object_bag
模块。 - 表(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();
}
总结
- 动态集合是基于动态字段的集合,灵活性极高。
- 不受对象大小限制,支持存储任意类型数据。
- 支持多种集合类型:包、对象包和表,满足不同场景需求。