Skip to content

动态字段

在 Sui 中,动态字段允许您将任意数据附加到对象。这使它们成为构建需要灵活数据存储的应用程序的强大工具。动态字段就像键值对一样工作,其中键是 vector<u8>,值可以是任何类型。这意味着您可以使用动态字段来存储各种数据,例如:

  • 角色的物品清单
  • 用户的个人资料信息
  • 游戏中物品的属性

动态字段类型

Sui 中有两种动态字段:

  1. dynamic_field:用于将任意数据附加到对象。
  2. dynamic_object_field:专门用于将对象附加到其他对象。

dynamic_field

dynamic_field 可用于将任何类型的数据附加到对象。这包括基元类型、结构体,甚至其他动态字段。要使用 dynamic_field,您需要导入 sui::dynamic_field 模块,该模块通常使用别名 df

dynamic_object_field

dynamic_object_field 专门用于将对象附加到其他对象。它们只能存储具有 key 能力的对象。这是因为 dynamic_object_field 存储对象的 ID,而不是对象本身。要使用 dynamic_object_field,您需要导入 sui::dynamic_object_field 模块,该模块通常使用别名 dofofield

使用动态字段

sui::dynamic_fieldsui::dynamic_object_field 模块都提供了一组用于管理动态字段的函数。一些最常见的函数包括:

  • add:向对象添加一个动态字段。
  • remove:从对象中移除一个动态字段。
  • borrow:从对象中借用一个动态字段。
  • borrow_mut:从对象中借用一个动态字段的可变引用。
  • contains:检查一个动态字段是否存在于对象中。
  • id:返回动态对象字段中存储的对象的 ID。

所有动态集合类型都支持 borrowborrow_mut 方法的索引语法。如果您在示例中看到方括号,它们将转换为 borrowborrow_mut 调用。

move
let hat: &Hat = &bag[b"key"];
let hat_mut: &mut Hat = &mut bag[b"key"];

// 等价于
let hat: &Hat = bag.borrow(b"key");
let hat_mut: &mut Hat = bag.borrow_mut(b"key");

动态字段示例

以下是如何使用 dynamic_field 将帽子和胡子附加到角色对象的示例:

move
use sui::dynamic_field as df;
use std::string::String;

// Struct representing a character to which dynamic fields are attached
public struct Character has key {
    id: UID 
}

// Different accessories that can be attached to the character
// They must have the `store` ability
public struct Hat has key, store {
    id: UID,
    color: u32 
}

public struct Beard has key, store {
    id: UID 
}

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

    // Attach a hat to the character's UID
    df::add(
        &mut character.id, 
        b"hat_key",
        Hat { id: object::new(ctx), color: 0xFF0000 }
    );

    // Similarly, attach a beard to the character's UID
    df::add(
        &mut character.id,
        b"beard_key",
        Beard { id: object::new(ctx) }
    );

    // Verify the hat and beard are attached to the character
    assert!(df::exists_(&character.id, b"hat_key"), 0);
    assert!(df::exists_(&character.id, b"beard_key"), 1);

    // Modify the color of the hat
    let hat: &mut Hat = df::borrow_mut(&mut character.id, b"hat_key");
    hat.color = 0x00FF00;

    // Remove the hat and beard from the character
    let hat: Hat = df::remove(&mut character.id, b"hat_key");
    let beard: Beard = df::remove(&mut character.id, b"beard_key");

    // Verify the hat and beard are no longer attached to the character
    assert!(!df::exists_(&character.id, b"hat_key"), 0);
    assert!(!df::exists_(&character.id, b"beard_key"), 1);

    sui::test_utils::destroy(character);
    sui::test_utils::destroy(beard);
    sui::test_utils::destroy(hat);
}

在这个例子中,我们定义了一个 Character 对象和两种不同类型的配件。由于配件具有 store 能力,因此可以使用动态字段将它们存储在 Character 对象中。

孤儿对象

当您从父对象中删除动态字段时,附加到该字段的对象将成为孤儿对象。孤儿对象不再与任何对象关联,因此无法再访问。要避免创建孤儿对象,您应该在删除父对象之前删除所有动态字段。

以下是如何创建孤儿对象的示例:

move
let hat = Hat { id: object::new(ctx), color: 0xFF0000 };
let mut character = Character { id: object::new(ctx) };

// Attach a `Hat` using a `vector<u8>` key
df::add(&mut character.id, b"hat_key", hat);

// ! Do not do this in your code
// ! Dangerous - deleting the parent object
let Character { id } = character;
id.delete();

// ...`Hat` is now stranded and can never be accessed again

字段名称

动态字段的名称是 vector<u8>。这意味着您可以使用任何字节字符串作为动态字段的名称。但是,在大多数情况下,您应该使用描述性名称来使您的代码更易于阅读。

以下是一些动态字段名称的示例:

  • b"name"
  • b"description"
  • b"items"

您还可以使用更复杂的结构体作为字段名称。例如,您可以使用以下结构体来表示物品的键:

move
struct ItemKey {
    name: String,
    id: u64,
}

最佳实践

以下是一些使用动态字段的最佳实践:

  • 使用描述性名称来使您的代码更易于阅读。
  • 在删除父对象之前删除所有动态字段,以避免创建孤儿对象。
  • 谨慎使用动态字段。它们是一个强大的工具,但如果使用不当,它们会导致代码复杂且难以维护。

总结

动态字段是一个强大的工具,可以用来构建需要灵活数据存储的应用程序。通过理解动态字段的工作方式,您可以构建更强大和更通用的应用程序。

以上内容遵循最新的 Move 2024 语法规范,且代码部分仅保留注释为中文。