Sui オブジェクトモデルでは、オブジェクトを他のオブジェクトに ダイナミックフィールド として添付することができます。この動作は、他のプログラミング言語の Map
の動作に似ています。しかし、Move では厳密に型付けされる Map
(コレクションのセクションで説明しました)とは異なり、ダイナミックフィールドでは任意の型のオブジェクトを添付することができます。フロントエンド開発の世界からの類似のアプローチとしては、任意の型のデータを動的に格納できる JavaScript の Object 型があります。
オブジェクトに添付できるダイナミックフィールドの数に制限はありません。したがって、ダイナミックフィールドを使用して、オブジェクトのサイズ制限に収まらない大量のデータを格納することができます。
ダイナミックフィールドは、オブジェクトサイズの制限を回避するためにデータを小さな部分に分割することから、アプリケーションロジックの一部としてオブジェクトを添付することまで、幅広い用途に使用できます。
ダイナミックフィールドは、Sui フレームワークの sui::dynamic_field
モジュールで定義されています。これらは 名前 を介してオブジェクトの UID
に添付され、その名前を使用してアクセスできます。オブジェクトに添付できる同じ名前のフィールドは1つだけです。
ファイル: sui-framework/sources/dynamic_field.move
/// フィールドと値を格納するための内部オブジェクト
public struct Field<Name: copy + drop + store, Value: store> has key {
/// オブジェクトID、フィールド名の値、その型のハッシュによって決定される
/// つまり、hash(parent.id || name || Name)
id: UID,
/// このフィールドの名前の値
name: Name,
/// このフィールドに紐づけられた値
value: Value,
}
定義が示すように、ダイナミックフィールドは内部の Field
オブジェクトに格納され、オブジェクト ID、フィールド名、フィールドの型に基づいて決定論的に生成された UID
を持ちます。Field
オブジェクトには、フィールド名とそれに紐づけられた値が含まれています。Name
と Value
の型パラメータの制約は、キーと値が持つべき能力を定義しています。
ダイナミックフィールドで利用可能なメソッドは簡単です:フィールドは add
で追加し、remove
で削除し、borrow
と borrow_mut
で読み取ることができます。さらに、exists_
メソッドを使用してフィールドが存在するかどうかを確認できます(型でより厳密にチェックするには、exists_with_type
メソッドがあります)。
module book::dynamic_collection {
// `dynamic_field` の非常に一般的なエイリアスは `df` です
// モジュール名がかなり長いため
use sui::dynamic_field as df;
use std::string::String;
/// ダイナミックフィールドを添付するオブジェクト。
public struct Character has key {
id: UID
}
// キャラクターに添付できる様々なアクセサリーのリスト。
// これらは `store` 能力を持つ必要があります。
public struct Hat has key, store { id: UID, color: u32 }
public struct Mustache 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) };
// キャラクターの UID に帽子を添付
df::add(
&mut character.id,
b"hat_key",
Hat { id: object::new(ctx), color: 0xFF0000 }
);
// 同様に、キャラクターの UID に口ひげを添付
df::add(
&mut character.id,
b"mustache_key",
Mustache { id: object::new(ctx) }
);
// 帽子と口ひげがキャラクターに添付されていることを確認
//
assert!(df::exists_(&character.id, b"hat_key"), 0);
assert!(df::exists_(&character.id, b"mustache_key"), 1);
// 帽子の色を変更
let hat: &mut Hat = df::borrow_mut(&mut character.id, b"hat_key");
hat.color = 0x00FF00;
// キャラクターから帽子と口ひげを削除
let hat: Hat = df::remove(&mut character.id, b"hat_key");
let mustache: Mustache = df::remove(&mut character.id, b"mustache_key");
// 帽子と口ひげがもはやキャラクターに添付されていないことを確認
assert!(!df::exists_(&character.id, b"hat_key"), 0);
assert!(!df::exists_(&character.id, b"mustache_key"), 1);
sui::test_utils::destroy(character);
sui::test_utils::destroy(mustache);
sui::test_utils::destroy(hat);
}
}
上の例では、Character
オブジェクトと、ベクターに一緒に入れることができない2つの異なるタイプのアクセサリーを定義しています。しかし、ダイナミックフィールドを使用すると、これらを1つのオブジェクトに一緒に格納することができます。両方のオブジェクトは vector<u8>
(バイト文字列リテラル)を介して Character
に添付され、それぞれのキーを使用してアクセスできます。
ご覧のように、アクセサリーを Character に添付する際、値で 渡しました。言い換えれば、両方の値は新しいスコープに移動され、その所有権は Character
オブジェクトに移転されました。Character
オブジェクトの所有権を変更した場合、アクセサリーも一緒に移動されます。
そして、ダイナミックフィールドの最後の重要な特性として強調すべきは、それらが 親を通じてアクセスされる ということです。これは、Hat
と Mustache
オブジェクトが直接アクセス可能ではなく、親オブジェクトと同じルールに従うことを意味します。
ダイナミックフィールドにより、オブジェクトは他のモジュールで定義された型を含む、任意の型のデータを保持することができます。これは、ダイナミックフィールドの汎用的な性質と、型パラメータに対する比較的弱い制約のおかげです。これを説明するために、Character
オブジェクトにいくつかの異なる値を添付してみましょう。
let mut character = Character { id: object::new(ctx) };
// `vector<u8>` 名を介して `String` を添付
df::add(&mut character.id, b"string_key", b"Hello, World!".to_string());
// `u32` 名を介して `u64` を添付
df::add(&mut character.id, 1000u32, 1_000_000_000u64);
// `bool` 名を介して `bool` を添付
df::add(&mut character.id, true, false);
この例では、ダイナミックフィールドの 名前 と 値 の両方に異なる型を使用できることを示しました。String
は vector<u8>
名を介して添付され、u64
は u32
名を介して添付され、bool
は bool
名を介して添付されています。ダイナミックフィールドでは何でも可能です!
孤立したダイナミックフィールドを防ぐために、Bag のような動的コレクション型を使用してください。これらはダイナミックフィールドを追跡し、添付されたフィールドがある場合はアンパッキングを許可しません。