Skip to main content

表格和袋子

你可以使用动态字段扩展现有对象。请注意,可以删除仍然具有(可能是非 drop 的)动态字段的对象。当向对象添加少量静态已知的附加字段时,这可能不是一个问题,但对于可能包含作为动态字段的键值对的无限多个链上集合类型而言,这是非常不希望的。

本主题介绍了两个这样的集合——表格和袋子——使用动态字段构建,但具有额外的支持,以计算它们包含的条目数,并在非空时防止意外删除。

本节讨论的类型和函数已构建到 Sui 框架的模块中,分别为 tablebag。与动态字段一样,还有两者的 object_ 变体:ObjectTableobject_table 中,ObjectBagobject_bag 中。TableObjectTable 之间的关系,以及 BagObjectBag 之间的关系与字段和对象字段之间的关系相同:前者可以将任何 store 类型作为值,但在从外部存储查看时隐藏了作为值存储的对象。后者只能将对象作为值存储,但在外部存储中以其 ID 保持这些对象可见。

表格

module sui::table {

struct Table<K: copy + drop + store, V: store> has key, store { /* ... */ }

public fun new<K: copy + drop + store, V: store>(
ctx: &mut TxContext,
): Table<K, V>;

}

Table<K, V> 是一个同质映射,这意味着所有其键都具有相同的类型(K),而所有其值也都具有相同的类型(V)。它通过 sui::table::new 创建,这需要对 &mut TxContext 的访问,因为 Table 本身也是对象,可以像任何其他对象一样进行传输、共享、封装或解封。

有关 Table 的保留对象版本,请参阅 sui::object_table::ObjectTable

Bags

module sui::bag {

struct Bag has key, store { /* ... */ }

public fun new(ctx: &mut TxContext): Bag;

}

Bag 是一个异质映射,因此它可以容纳任意类型的键值对(它们不需要匹配)。请注意,由于这个原因,Bag 类型没有任何类型参数。与 Table 一样,Bag 也是一个对象,因此使用 sui::bag::new 创建一个需要提供 &mut TxContext 以生成 ID。

有关 Bag 的保留对象版本,请参阅 sui::bag::ObjectBag


以下部分解释了集合的 API。它们以 sui::table 为代码示例的基础,其中对其他模块有差异的地方进行了解释。

与集合交互

所有集合类型都提供了以下函数,定义在它们各自的模块中:

module sui::table {

public fun add<K: copy + drop + store, V: store>(
table: &mut Table<K, V>,
k: K,
v: V,
);

public fun borrow<K: copy + drop + store, V: store>(
table: &Table<K, V>,
k: K
): &V;

public fun borrow_mut<K: copy + drop + store, V: store>(
table: &mut Table<K, V>,
k: K
): &mut V;

public fun remove<K: copy + drop + store, V: store>(
table: &mut Table<K, V>,
k: K,
): V;

}

这些函数分别向集合添加、读取、写入和移除条目,并且都接受按值传递的键。Table 具有类型参数 KV,因此不可能在相同的 Table 实例上使用 KV 的不同实例调用这些函数,但是 Bag 没有这些类型参数,因此允许在相同实例上使用不同实例进行调用。

info

与动态字段一样,尝试覆盖现有键、访问或移除不存在的键都是错误的。

Bag 的异构性的额外灵活性意味着类型系统不会在静态上阻止尝试以一种类型添加值,然后以另一种类型借用或删除它。这种模式在运行时失败,类似于对动态字段的行为。

查询长度

可以使用以下一组函数查询所有集合类型的长度,并检查它们是否为空:

module sui::table {

public fun length<K: copy + drop + store, V: store>(
table: &Table<K, V>,
): u64;

public fun is_empty<K: copy + drop + store, V: store>(
table: &Table<K, V>
): bool;

}

Bag 有这些函数,但它们不是关于 KV 的泛型,因为 Bag 没有这些类型参数。

查询包含性

可以使用以下函数查询表格中是否包含键:

module sui::table {

public fun contains<K: copy + drop + store, V: store>(
table: &Table<K, V>
k: K
): bool;

}

对于 Bag 的等效函数是:

module sui::bag {

public fun contains<K: copy + drop + store>(bag: &Bag, k: K): bool;

public fun contains_with_type<K: copy + drop + store, V: store>(
bag: &Bag,
k: K
): bool;

}

第一个函数测试 bag 是否包含键为 k: K 的键值对,第二个函数还会测试其值是否具有类型 V

清理

集合类型防止在可能不为空时意外删除。这种保护来自于它们没有 drop 的事实,因此必须显式删除,使用以下 API:

module sui::table {

public fun destroy_empty<K: copy + drop + store, V: store>(
table: Table<K, V>,
);

}

此函数按值接收集合。如果它不包含任何条目,则将其删除,否则调用将失败。sui::table::Table 也有一个便利函数:

module sui::table {

public fun drop<K: copy + drop + store, V: drop + store>(
table: Table<K, V>,
);

}

只能为具有 drop 能力的值类型的表格调用此便利函数,这使得它能够删除表格,无论它们是否为空。

请注意,合适的表格在离开范围之前不会隐式调用 drop。它必须显式调用,但保证在运行时成功。

BagObjectBag 无法支持 drop,因为它们可以包含各种类型,其中一些可能具有 drop,而另一些可能没有。

ObjectTable 不支持 drop,因为它的值必须是对象,而对象不能被丢弃(因为它们必须包含一个 id: UID 字段,而 UID 没有 drop)。

相等性

对集合的相等性基于标识,例如,集合类型的实例仅被视为与自身相等,而不与包含相同条目的所有集合相等:

let t1 = sui::table::new<u64, u64>(ctx);
let t2 = sui::table::new<u64, u64>(ctx);

assert!(&t1 == &t1, 0);
assert!(&t1 != &t2, 1);

这可能不是你想要的相等性定义。