🌱
Dev Compendium
  • Ethereum
    • Solidity
      • EVM
      • Architecture
      • Execution Context
      • Transactions
      • Gas
      • Calldata, Memory & Storage
      • Gas Optimisation
      • Function Declarations
      • receive() & fallback()
      • CALL vs. DELEGATE CALL
    • Yul
      • Introduction
      • Types
      • Basic Operations
      • Storage
      • Memory
        • Arrays
        • Structs
        • Tuples, revert, keccak256
        • Logs and Events
        • Gotchas
        • abi.encode
      • Calldata
        • External Calls
        • Dynamic Length Inputs
        • Transferring Value
        • Receiving Contract Calls
      • Contracts in Yul
      • Other Yul Functions
    • Foundry
    • Security
      • Common Vulnerabilities
      • Best Practices
      • Development Workflow
      • Contract Migration
    • Auditing Tools
      • Slither
      • Mythril
      • Fuzzing
    • Upgradable Contracts
      • Upgrade Patterns
      • ERC-1967 Implementation
      • Deployment
    • MEV
    • Tooling
      • Chainlink
      • IPFS
      • Radicle
    • Frontend
      • Contract Hooks
      • Wallet Connection
        • wagmi.sh
        • Rainbow Kit
      • thirdweb
    • Protocol Research
      • Uniswap v2
      • Uniswap v3
      • Curve
      • GMX
  • Starkware
    • Fundamentals
    • Account Abstraction
    • Universal Deployer
    • Cairo 1.0
    • starknet.js
    • Security Model
  • Zero Knowledge
    • Group Theory
    • ECDSA
  • Rust
    • Basic Operations
    • Set up
    • Primitives
    • Control Flow
    • Mutability & Shadowing
    • Adding Behavior
    • Lifetimes
    • Std Library
  • SUI
    • Architecture
    • Consensus Mechanism
    • Local Node Setup
    • Sui Client CLI
    • Move Contracts
      • Move
      • Move.toml
      • Move.lock
      • Accessing Time in Sui Move
      • Set up Development Framework
      • Debug & Publish
      • Package Upgrades
      • Sui Move Library
      • Difference from Core Move
    • Object Programming
      • Object Basics
      • Using Objects
      • Immutable Objects
      • Object Wrapping
      • Dynamic Fields
      • Collections
      • Unit Testing
      • Deployment with CLI
  • NEAR
    • Architecture
    • Contract Standards
      • Fungible Token (NEP-141)
      • Non-Fungible Token (NEP-171)
      • Storage Management (NEP-145)
      • Events (NEP-297)
      • Meta-Transactions
    • Rust Contracts
      • Development Workflow
      • Smart Contract Layout
      • Storage Management
      • Events & Meta-transactions
      • Method Types
      • Upgrading Contracts
      • Unit Testing
    • NEAR Libraries
    • Environment Variables
    • Serialisation
    • Security Concepts
    • Collections
    • JS SDK
Powered by GitBook
On this page
  1. SUI
  2. Object Programming

Collections

PreviousDynamic FieldsNextUnit Testing

Last updated 2 years ago

The previous chapter, , introduced a way to extend existing objects with dynamic fields. Note that it's possible to delete an object that still has (potentially non-drop) dynamic fields. This might not be a concern when adding a small number of statically known additional fields to an object, but is particularly undesirable for on-chain collection types which could be holding unboundedly many key-value pairs as dynamic fields.

This chapter describes two such collections -- Table and Bag -- built using dynamic fields, but with additional support to count the number of entries they contain, and protect against accidental deletion when non-empty.

The types and function discussed in this section are built into the Sui framework in modules and . As with dynamic fields, there is also an object_ variant of both: ObjectTable in and ObjectBag in . The relationship between Table and ObjectTable, and Bag and ObjectBag are the same as between a field and an object field: The former can hold any store type as a value, but objects stored as values are hidden when viewed from external storage. The latter can only store objects as values, but keeps those objects visible at their ID in external storage.

Tables

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> is a homogeneous map, meaning that all its keys have the same type as each other (K), and all its values have the same type as each other as well (V). It is created with sui::table::new, which requires access to a &mut TxContext because Tables are objects themselves, which can be transferred, shared, wrapped, or unwrapped, just like any other object.

See sui::object_table::ObjectTable for the object-preserving version of Table.

Bags

module sui::bag {

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

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

}

Bag is a heterogeneous map, so it can hold key-value pairs of arbitrary types (they don't need to match each other). Note that the Bag type does not have any type parameters for this reason. Like Table, Bag is also an object, so creating one with sui::bag::new requires supplying a &mut TxContext to generate an ID.

See sui::bag::ObjectBag for the object-preserving version of Bag.

The following sections explain the collection APIs. They use sui::table as the basis for code examples, with explanations where other modules differ.

Interacting with Collections

All collection types come with the following functions, defined in their respective modules:

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;

}

These functions add, read, write, and remove entries from the collection, respectively, and all accept keys by value. Table has type parameters for K and V so it is not possible to call these functions with different instantiations of K and V on the same instance of Table, however Bag does not have these type parameters, and so does permit calls with different instantiations on the same instance.

Note: Like with dynamic fields, it is an error to attempt to overwrite an existing key, or access or remove a non-existent key.

The extra flexibility of Bag's heterogeneity means the type system doesn't statically prevent attempts to add a value with one type, and then borrow or remove it at another type. This pattern fails at runtime, similar to the behavior for dynamic fields.

Querying Length

It is possible to query all collection types for their length and check whether they are empty using the following family of functions:

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 has these functions, but they are not generic on K and V because Bag does not have these type parameters.

Querying for Containment

All collections can be queried for key containment with:

module sui::table {

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

}

The equivalent function for Bag is,

module sui::bag {

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

}

This example tests whether bag contains a key-value pair with key k: K and some value of type V.

Clean-up

As mentioned in the introduction, collection types protect against accidental deletion when they might not be empty. This protection comes from the fact that they do not have drop, so must be explicitly deleted, using this API:

module sui::table {

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

}

This function takes the collection by value. If it contains no entries, it is deleted, otherwise the call fails. sui::table::Table also has a convenience function:

module sui::table {

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

}

You can call the convenience function only for tables where the value type also has drop ability, which allows it to delete tables whether they are empty or not.

Note that drop is not called implicitly on eligible tables before they go out of scope. It must be called explicitly, but it is guaranteed to succeed at runtime.

Bag and ObjectBag cannot support drop because they could be holding a variety of types, some of which may have drop and some which may not.

ObjectTable does not support drop because its values must be objects, which cannot be drop (because they must contain an id: UID field and UID does not have drop).

Equality

Equality on collections is based on identity, for example, an instance of a collection type is only considered equal to itself and not to all collections that hold the same entries:

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

assert!(&t1 == &t1, 0);
assert!(&t1 != &t2, 1);
Dynamic Fields
table
bag
object_table
object_bag