본문으로 건너뛰기

Table과 Bag

dynamic fields를 사용해 기존 object를 확장할 수 있으며, 여전히 dynamic field를 가진 object를 삭제하는 것도 가능하다(잠재적으로 non-drop인 경우 포함). 이것은 statically known된 소수의 추가 field를 object에 더하는 경우에는 문제가 아닐 수 있지만, key-value pair를 dynamic field로 무한정 많이 보관할 수 있는 온체인 collection type에서는 특히 바람직하지 않다.

이 주제는 dynamic fields를 사용해 구축되었지만, 포함된 entry 수를 세고 비어 있지 않을 때 accidental deletion으로부터 보호하는 추가 지원을 갖춘 collection인 table과 bag를 설명한다.

이 섹션에서 논의하는 type과 function은 Sui framework의 tablebag module에 built-in으로 포함되어 있다. dynamic field와 마찬가지로, 두 경우 모두 object_ variant도 있는데, object_tableObjectTableobject_bagObjectBag가 그것이다. TableObjectTable, BagObjectBag의 관계는 field와 object field의 관계와 같다: 전자는 어떤 store type이든 value로 담을 수 있지만, value로 저장된 object는 external storage에서 볼 때 숨겨진다. 후자는 value로 object만 저장할 수 있지만, external storage에서 그 object가 ID로 계속 보이게 한다.

Tables

Table<K, V>는 homogeneous map이며, 이는 모든 key가 서로 같은 type(K)이고 모든 value도 서로 같은 type(V)이라는 뜻이다. 이는 sui::table::new로 생성되며, Table 자체가 다른 어떤 object처럼 transfer, share, wrap, unwrap될 수 있는 object이기 때문에 &mut TxContext에 대한 접근이 필요하다.

Table object-preserving version은 sui::object_table::ObjectTable을 참조한다.

module sui::table;

public 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>;

Bags

Bag는 heterogeneous map이므로 임의의 type의 key-value pair를 담을 수 있고, 서로 type이 일치할 필요가 없다. 이 때문에 Bag type에는 type parameter가 없다. Table과 마찬가지로 Bag도 object이므로, sui::bag::new로 이를 만들려면 ID를 생성하기 위해 &mut TxContext를 제공해야 한다.

Bag object-preserving version은 sui::bag::ObjectBag를 참조한다.

module sui::bag;

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

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

Interacting with collections

모든 collection type에는 각자의 module에 정의된 다음 함수가 함께 제공된다:

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;

이 함수들은 각각 collection에 entry를 추가하고, 읽고, 쓰고, 제거하며, 모두 key를 값으로 받는다. TableKV에 대한 type parameter를 가지므로, 같은 Table instance에서 서로 다른 KV 인스턴스화로 이 함수를 호출하는 것은 불가능하다. 하지만 Bag는 이런 type parameter를 가지지 않으므로, 같은 instance에서 서로 다른 인스턴스화로 호출하는 것을 허용한다.

정보

dynamic field와 마찬가지로, 이미 존재하는 key를 덮어쓰거나 존재하지 않는 key에 접근하거나 제거하려는 시도는 error이다.

Bag의 heterogeneity가 주는 추가 유연성은 type system이 한 type의 value를 추가한 뒤 다른 type으로 이를 빌리거나 제거하려는 시도를 정적으로 막아주지 않는다는 뜻이다. 이 패턴은 dynamic field의 동작과 비슷하게 runtime에서 실패한다.

Querying length

다음 함수 계열을 사용해 모든 collection type의 길이를 질의하고 비어 있는지 확인할 수 있다:

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에도 이 함수들이 있지만, Bag는 그런 type parameter를 가지지 않으므로 KV에 대해 generic하지 않다.

Querying for containment

Table은 다음으로 key containment를 질의할 수 있다:

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에 key가 k: K인 key-value pair가 들어 있는지를 검사하고, 두 번째 함수는 그 value가 type V인지 검사한다.

Clean-up

collection type은 비어 있지 않을 수 있을 때 accidental deletion으로부터 보호한다. 이 보호는 그 type들이 drop을 가지지 않는다는 사실에서 오며, 따라서 이 API를 사용해 명시적으로 삭제해야 한다:

module sui::table;

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

이 함수는 collection을 값으로 받는다. entry가 전혀 없으면 삭제되고, 그렇지 않으면 호출이 실패한다. sui::table::Table에는 다음 convenience function도 있다:

module sui::table;

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

이 convenience function은 value type도 drop ability를 가진 table에 대해서만 호출할 수 있으며, 이 경우 table이 비어 있든 아니든 삭제할 수 있다.

eligible한 table이 scope를 벗어나기 전에 drop이 암묵적으로 호출되지는 않는다는 점에 유의한다. 반드시 명시적으로 호출해야 하지만, runtime에서는 성공이 보장된다.

BagObjectBag는 서로 다른 여러 type을 담을 수 있고 그중 일부는 drop을 가지지만 일부는 아닐 수 있으므로 drop을 지원할 수 없다.

ObjectTable은 value가 object여야 하고, object는 id: UID field를 포함해야 하며 UIDdrop을 가지지 않으므로 drop을 지원하지 않는다.

Equality

collection의 equality는 identity를 기반으로 하며, 예를 들어 collection type의 instance는 같은 entry를 가진 모든 collection과 같다고 간주되는 것이 아니라 자기 자신과만 같다고 간주된다:

use sui::table;

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

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

이것은 여러분이 원하는 equality 정의일 가능성이 낮다.