动态对象字段

本节扩展了动态字段。请先阅读它,以了解动态字段的基本知识。

动态字段的另一种变体是_动态对象字段_,它与常规动态字段有一些不同之处。在本节中,我们将介绍动态对象字段的具体细节,并解释它们与常规动态字段的区别。

一般建议是尽量避免使用动态对象字段,除非确实需要通过ID进行直接发现。动态对象字段的额外成本可能无法被其提供的好处所证明。

定义

动态对象字段在Sui框架sui::dynamic_object_fields模块中定义。它们在许多方面与动态字段相似,但与动态字段不同,动态对象字段对Value类型有额外的约束。Value必须具有keystore的组合,而不仅仅是动态字段中的store

它们在框架定义中不那么明确,因为这个概念本身更为抽象:

文件:sui-framework/sources/dynamic_object_fields.move

/// 用于存储字段和值的内部对象
public struct Wrapper<Name> has copy, drop, store {
    name: Name,
}

动态字段部分中的Field类型不同,Wrapper类型仅存储字段的名称。值是对象本身,未被包装

Value类型的约束在动态对象字段可用的方法中变得明显。以下是add函数的签名:

/// 将动态对象字段添加到对象`object: &mut UID`上的由`name: Name`指定的字段中。
/// 如果对象已经具有该名称的字段,则中止并返回`EFieldAlreadyExists`。
public fun add<Name: copy + drop + store, Value: key + store>(
    // 我们在多个地方使用&mut UID进行访问控制
    object: &mut UID,
    name: Name,
    value: Value,
) { /* 实现省略 */ }

其余与动态字段部分中相同的方法对Value类型有相同的约束。我们列出它们以供参考:

  • add - 向对象添加动态对象字段
  • remove - 从对象中删除动态对象字段
  • borrow - 从对象中借用动态对象字段
  • borrow_mut - 从对象中借用动态对象字段的可变引用
  • exists_ - 检查动态对象字段是否存在
  • exists_with_type - 检查特定类型的动态对象字段是否存在

此外,还有一个id方法,它返回Value对象的ID,而不指定其类型。

用法及与动态字段的区别

动态字段和动态对象字段之间的主要区别在于后者只允许将_对象_作为值进行存储。这意味着你不能存储u64bool等原始类型。尽管如此,动态对象字段并未被包装成一个单独的对象,这种约束可以被看作一种限制。

放宽包装的要求使对象可通过其ID进行链外发现。然而,如果实现了包装对象索引,这一特性可能不再出色,从而使动态对象字段成为冗余特性。

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: b"John".to_string()
        });

        // 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.
        sui::test_utils::destroy(hat);
        sui::test_utils::destroy(character);
    }
}

定价差异

动态对象字段比动态字段稍微昂贵一些。由于其内部结构,它们需要两个对象:名称的包装器和值。因此,添加和访问对象字段的成本(加载2个对象相比于动态字段的1个对象)更高。

下一步

动态字段和动态对象字段都是强大的功能,允许在应用程序中实现创新的解决方案。然而,它们相对低级,需要仔细处理以避免孤立字段。在下一节中,我们将介绍一个更高级别的抽象 - 动态集合,它可以更有效地管理动态字段和对象。