llms.txt
Skip to main content

Dynamic Object Fields

This section expands on Dynamic Fields. Please read it first to understand the basics of dynamic fields.

Another variation of dynamic fields is dynamic object fields, which have certain differences from regular dynamic fields. In this section, we will cover the specifics of dynamic object fields and explain how they differ from regular dynamic fields.

The general recommendation is to avoid using dynamic object fields in favor of (just) dynamic fields, especially if there's no need for direct discovery through the ID. The extra costs of dynamic object fields may not be justified by the benefits they provide.

Definition

Dynamic Object Fields are defined in the sui::dynamic_object_field module in the Sui Framework. They are similar to dynamic fields in many ways, but unlike them, dynamic object fields have an extra constraint on the Value type. The Value must have a combination of key and store, not just store as in the case of dynamic fields.

The module definition is smaller than that of dynamic fields - only the field name gets a wrapper type, while the value is stored as-is:

module sui::dynamic_object_field;

/// Internal object used for storing the field and the name associated with the
/// value. The separate type is necessary to prevent key collision with direct
/// usage of dynamic_field
public struct Wrapper<Name> has copy, drop, store {
name: Name,
}

Unlike the Field type in the Dynamic Fields section, the Wrapper type only stores the name of the field. The value is the object itself, and is not wrapped.

See full documentation for sui::dynamic_object_field module.

The constraints on the Value type become visible in the methods available for dynamic object fields. Here's the signature for the add function:

/// Adds a dynamic object field to the object `object: &mut UID` at field
/// specified by `name: Name`. Aborts with `EFieldAlreadyExists` if the object
/// already has that field with that name.
public fun add<Name: copy + drop + store, Value: key + store>(
// we use &mut UID in several spots for access control
object: &mut UID,
name: Name,
value: Value,
) { /* implementation omitted */ }

The rest of the methods are identical to the ones in the Dynamic Fields section, and carry the same constraint on the Value type. Let's list them for reference:

  • add - adds a dynamic object field to the object
  • remove - removes a dynamic object field from the object
  • borrow - borrows a dynamic object field from the object
  • borrow_mut - borrows a mutable reference to a dynamic object field from the object
  • exists_ - checks if a dynamic object field exists
  • exists_with_type - checks if a dynamic object field exists with a specific type

Additionally, there is an id method which returns the ID of the Value object without specifying its type.

Usage and Differences with Dynamic Fields

The main difference between dynamic fields and dynamic object fields is that the latter allows storing only objects as values. This means that you can't store primitive types like u64 or bool. In exchange for this restriction, the attached object is not wrapped into a separate object: it keeps its ID and stays visible to offchain tooling.

This is the property to weigh when choosing between the two: a value attached as a regular dynamic field is wrapped into a Field object and disappears from ID-based queries, while a value attached as a dynamic object field remains discoverable by its ID in wallets and explorers.

module book::dynamic_object_field;

use std::string::String;

// there are two common aliases for the long module name: `dof` and
// `ofield`. Both are commonly used and met in different projects.
use sui::dynamic_object_field as dof;
use sui::dynamic_field as df;

/// The `Character` that we will use for the example
public struct Character has key { id: UID }

/// Metadata that doesn't have the `key` ability
public struct Metadata has store, drop { name: String }

/// Accessory that has the `key` and `store` abilities.
public struct Accessory has key, store { id: UID }

#[test]
fun equip_accessory() {
let ctx = &mut tx_context::dummy();
let mut character = Character { id: object::new(ctx) };

// Create an accessory and attach it to the character
let hat = Accessory { id: object::new(ctx) };

// Add the hat to the character. Just like with `dynamic_fields`
dof::add(&mut character.id, b"hat_key", hat);

// However for non-key structs we can only use `dynamic_field`
df::add(&mut character.id, b"metadata_key", Metadata {
name: "John"
});

// Borrow the hat from the character
let hat_id = dof::id(&character.id, b"hat_key").extract(); // Option<ID>
let hat_ref: &Accessory = dof::borrow(&character.id, b"hat_key");
let hat_mut: &mut Accessory = dof::borrow_mut(&mut character.id, b"hat_key");
let hat: Accessory = dof::remove(&mut character.id, b"hat_key");

// Clean up, Metadata is an orphan now.
std::unit_test::destroy(hat);
std::unit_test::destroy(character);
}

In the example above, the Accessory has both key and store, so it can be attached as a dynamic object field. The Metadata, however, only has store, so it can only be attached as a regular dynamic field. Both kinds of fields coexist on the same UID - even under similar names - because the internal Wrapper type prevents key collisions between the two modules. Lastly, the example demonstrates the id function, which returns the ID of the attached object without requiring its type - something only possible because the object keeps its identity.

Pricing Differences

Dynamic object fields are a little more expensive than dynamic fields. Because of their internal structure, a single dynamic object field is stored as two objects: an internal field storing the name, and the value object itself. As a result, the cost of adding and accessing dynamic object fields (loading 2 objects compared to 1 for dynamic fields) is higher.

Summary

  • Dynamic object fields require the value to be an object (key + store) and, unlike regular dynamic fields, keep the attached object discoverable by its ID in wallets and explorers.
  • The methods mirror those of dynamic fields, with an extra id function that returns the ID of the attached object without specifying its type.
  • Dynamic object fields are more expensive than dynamic fields, so prefer the latter unless ID-based discovery is required.

Next Steps

Both dynamic fields and dynamic object fields are powerful features which allow for innovative solutions in applications. However, they are relatively low-level and require careful handling to avoid orphaned fields. In the next section, we will introduce a higher-level abstraction - Dynamic Collections - which can help with managing dynamic fields and objects more effectively.

Further Reading

llms.txt