メインコンテンツまでスキップ

Abilities(機能)

Abilitiesは、Moveの型指定機能であり、特定の型の値に対して許可されるアクションを制御します。このシステムにより値の"linear"typing動作、及びグローバルストレージでの値の使用の有無と使用方法をきめ細かく制御出来ます。これは、特定のバイトコード指示へのアクセスをゲートすることで実装されます。そのため値がバイトコード指示で使用されるため必要とするAbilityが必要(必要な場合、全ての指示がAbilityによってゲートされるわけではありません)。

4つのAbility

4つのAbilityは:

  • copy
    • このAbilityを持つ型の値をコピー出来る様にします。
  • drop
    • このAbilityを持つ型の値をポップ/ドロップ出来る様にします。
  • store
    • このAbilityを持つ型の値がグローバルストレージ内の構造体内に存在出来る様にします。
  • key
    • 型をグローバルストレージ操作のキーとして提供出来る様にします。

コピー

このcopy機能により、その機能を持つ型の値をコピー出来ます。これは、copy演算子を使用してローカル変数外の値をコピーする機能をゲートし、逆参照*eを使用して参照経由で値をコピーします。

値がcopyを持つ場合、その値内に含まれる全ての値はcopyとなります。

ドロップ

このdrop機能により、その機能を持つ型の値をドロップできます。ドロップとは、値が転送されずMoveプログラムの実行時に実質的に破棄される事を意味します。そのためこの機能は、以下を含む多くの場所で値を無視する機能を制限します。

値がdropした場合、その値内に含まれる全ての値はdropします。

ストア

store機能により、この機能を持つ型の値がグローバルストレージ内の構造体(リソース)内へ存在出来る様になりますが、グローバルストレージ内の最上位のリソースとして存在出来るとは限りません。これは操作を直接ゲートしない唯一の機能です。そのかわりkeyと併用で使用された場合、グローバルストレージの存在をゲートします。

値がstoreである場合, その値が含む全ての値はstoreです。

キー

key機能により、型をグローバルストレージ操作のキーとして提供します。これは全てのグローバルストレージ操作をゲートし、型をmove_toborrow_globalmove_from等で使用するには、型はkey機能が必要です。

注意: 操作はkey型が定義されているモジュールで使用する必要が有ります。(ある意味で、操作は定義モジュールに対してプライベートです)。

値がkeyである場合、その値が含む全ての値はstoreとなります。これは、この種の非対称性を持つ唯一の機能です。

組み込み型

殆どのプリミティブな組み込み型にはcopydropstore が有りますが、signerには無くただdropが有ります。

  • boolu8u16u32u64u128u256addressの全てがcopydropstoreを持ちます。
  • signerdropを持ちます。
    • コピー出来ず、グローバルストレージへ保存出来ません。  
  • Tの機能に応じてvector<T>copydropstoreを持つ場合が有ります。
  • 不変参照の&と可変参照の&mutはどちらも copydropを持ちます。
    • これは、参照先ではなく、参照内容自体をコピーして削除する事を指します。
    • 参照内容はグローバルストレージへ出現出来ないため、storeは有りません。

プリミティブ型にはkeyが無いためグローバルストレージ操作で直接使用する事は出来ません。

構造体の注釈

structが機能を持つ事を宣言するには、構造体名の後でありフィールドの前へhas <ability>を宣言します。例:

struct Ignorable has drop { f: u64 }
struct Pair has copy, drop, store { x: u64, y: u64 }

この場合: Ignorabledrop機能が有ります。 Paircopydropstore機能が有ります。

これらの機能は全て、ゲートされた操作に対して強力な保証を持っています。値が他のコレクション内に深くネストされている場合でも、その機能がある場合のみ値の上で操作を実行できます。

つまり、構造体の能力を宣言する場合、フィールドに特定の要件が課されます。すべてのフィールドがこれらの制約を満たす必要があります。これらのルールは、構造体が上記の機能の到達可能性ルールを満たすため必要です。構造体がその機能付きで宣言されている場合は...

  • copy, 全てのフィールドはcopyが必要です。
  • drop, 全てのフィールドはdropが必要です。
  • store, 全てのフィールドはstoreが必要です。
  • key, 全てのフィールドはstoreが必要です。
    • key それ自体が必要としない現時点で唯一の機能です。

例えば:

// 何の機能も持たない構造体
struct NoAbilities {}

struct WantsCopy has copy {
f: NoAbilities, // エラー 'NoAbilities'には'copy'が有りません。
}

同様に:

// 何の機能も持たない構造体
struct NoAbilities {}

struct MyResource has key {
f: NoAbilities, // Error 'NoAbilities'には'store'が有りません。
}

条件付き機能とジェネリック型

ジェネリック型に機能が注釈付けされている場合、その型の全てのインスタンスがその機能を持つ事が保証されるわけではありません。この構造体宣言を検討して下さい。

struct Cup<T> has copy, drop, store, key { item: T }

機能に関係なくCupが任意の型を保持出来ると非常に便利です。型システムは型パラメータを 見る 事が出来るため、その能力の保証に違反する型パラメータを見つけた場合は、Cupから機能を削除できるはずです。

この動作は最初は少し分かりにくいかもしれませんが、コレクション型について考えると理解し易いかもしれません。組み込み型のvectorは以下の型宣言が有ると考える事が出来ます。

vector<T> has copy, drop, store;

vectorはどの型でも動作する様にしたいです。異なる機能ごとにvector型を分けたくありません。では、必要なルールは何でしょうか? 上記のフィールドルールで必要なルールと全く同じです。つまり、内部要素をコピー出来る場合のみvector値をコピーしても安全です。内部要素を無視/削除できる場合のみvector値を無視しても安全です。

また、内部要素をグローバルストレージへ配置出来る場合のみvectorをグローバルストレージへ配置しても安全です。

この追加の表現力を持たせるため、型はその型のインスタンス化に応じて宣言された全ての機能を持つとは限りません。そのかわり、型が持つ機能はその宣言 型引数次第です。どの型でも悲観的に、型パラメータは構造体内で使用されると想定されるため、型パラメータがフィールドに対して上記で説明した要件を満たしている場合のみ機能が付与されます。上記の例からCupを取り上げます。

  • CupTcopyを持っている場合のみcopy機能を持ちます。
  • Tdropを持つ場合のみdropを持ちます。
  • Tstoreを持つ場合のみstoreを持ちます。
  • Tstoreを持つ場合のみkeyを持ちます。

各機能の条件付きシステムの例がこちら。

例: 条件付きcopy

struct NoAbilities {}
struct S has copy, drop { f: bool }
struct Cup<T> has copy, drop, store { item: T }

fun example(c_x: Cup<u64>, c_s: Cup<S>) {
// 有効, 'u64'が'copy'を持っているので'Cup<u64>'は'copy'を持っています。
let c_x2 = copy c_x;
// 有効, 'S'が'copy'を持っているので'Cup<S>'は'copy'を持っています。
let c_s2 = copy c_s;
}

fun invalid(c_account: Cup<signer>, c_n: Cup<NoAbilities>) {
// 無効, 'Cup<signer>'は'copy'を持っていません。
// 'Cup'がcopyで宣言されているにも関わらず、インスタンスには'copy'が有りません。
// 'signer'には'copy'が無いため
let c_account2 = copy c_account;
// 無効, 'Cup<NoAbilities>'には'copy'が有りません。
// 'NoAbilities'には'copy'が無いため
let c_n2 = copy c_n;
}

例: 条件付きdrop

struct NoAbilities {}
struct S has copy, drop { f: bool }
struct Cup<T> has copy, drop, store { item: T }

fun unused() {
Cup<bool> { item: true }; // 有効, 'Cup<bool>'には'drop'が有ります。
Cup<S> { item: S { f: false }}; // 有効, 'Cup<S>'には'drop'が有ります。
}

fun left_in_local(c_account: Cup<signer>): u64 {
let c_b = Cup<bool> { item: true };
let c_s = Cup<S> { item: S { f: false }};
// 有効なリターン: 'c_account'、'c_b'、'c_s'には値が有ります。
// ただし'Cup<signer>'、'Cup<bool>'、'Cup<S>'には'drop'が有ります。
0
}

fun invalid_unused() {
// 無効, 'drop'が無いため'Cup<NoAbilities>'は無視出来ません。
// 'Cup'が'drop'で宣言されているにも関わらずインスタンスには'drop'が有りません。
// 'NoAbilities'には'drop'が無いため。
Cup<NoAbilities> { item: NoAbilities {}};
}

fun invalid_left_in_local(): u64 {
let c_n = Cup<NoAbilities> { item: NoAbilities {}};
// 無効なリターン: 'c_n'には値が有ります。
// 'Cup<NoAbilities>'には'drop'が有りません。

0
}

例: 条件付き store

struct Cup<T> has copy, drop, store { item: T }

// 'MyInnerResource'は'store'で宣言されているため全てのフィールドは'store'が必要です。
struct MyInnerResource has store {
yes: Cup<u64>, // 有効, 'Cup<u64>'には'store'が有ります。
// no: Cup<signer>,無効, 'Cup<signer>'には'store'が有りません。
}

// 'MyResource'は'key'で宣言されているため、全てのフィールドは'store'が必要です。
struct MyResource has key {
yes: Cup<u64>, // 有効, 'Cup<u64>'には'store'が有ります。
inner: Cup<MyInnerResource>, // 有効, 'Cup<MyInnerResource>'には'store'が有ります。
// no: Cup<signer>, 無効, 'Cup<signer>'には'store'が有りません。
}

例: 条件付き key

struct NoAbilities {}
struct MyResource<T> has key { f: T }

fun valid(account: &signer) acquires MyResource {
let addr = signer::address_of(account);
// 有効, 'MyResource<u64>'には'key'が有ります。
let has_resource = exists<MyResource<u64>>(addr);
if (!has_resource) {
// 有効, 'MyResource<u64>'には'key'が有ります。
move_to(account, MyResource<u64> { f: 0 })
};
// 有効, 'MyResource<u64>'には'key'が有ります。
let r = borrow_global_mut<MyResource<u64>>(addr)
r.f = r.f + 1;
}

fun invalid(account: &signer) {
// 無効, 'MyResource<NoAbilities>'には'key'が有りません。
let has_it = exists<MyResource<NoAbilities>>(addr);
// 無効, 'MyResource<NoAbilities>'には'key'が有りません。
let NoAbilities {} = move_from<NoAbilities>(addr);
// 無効, 'MyResource<NoAbilities>'には'key'が有りません。
move_to(account, NoAbilities {});
// 無効, 'MyResource<NoAbilities>'には'key'が有りません。
borrow_global<NoAbilities>(addr);
}