このブログは「Solidity開発者向けSui Move入門編」を読んで(2)の続きです
続き
型
Moveには2種類の型がある
- プリミティブ型
- 「古典的な」型のことで、例えばu64、bool、addressなど、一般的によく使われる基本的な型
- ジェネリック型
- コードの型の一貫性を保つために提供される型、具体的には定義されていない
ジェネリクス型
MoveはSolidityと違ってコードとデータが分離されている。なので関数を呼び出す時、必要なデータを全て渡す必要がある。
実行中の型の一貫性を保つため、柔軟な型システムが存在する、それがジェネリクス型。
typescriptmodule bakery::bread { struct Bread<BreadType> has key, store { id: UID, baked: bool, weight: u64, bread_type: BreadType } // さまざまな種類のパン struct Baguette has drop {} struct Bagel has drop {} struct Focaccia has drop {} // 生のパンを作るジェネリック関数 public fun prepare<BreadType: drop>( weight: u64, bread_type: BreadType, ctx: &mut TxContext ): Bread<BreadType> { return Bread<BreadType> { id: object::new(ctx), baked: false, weight: weight, bread_type: bread_type, }; } // パンを焼くジェネリック関数 public fun bake<BreadType: drop>( bread: &mut Bread<BreadType> ): Bread<BreadType> { bread.baked = true; return bread; } }
この例では、<BreadType>というジェネリック型を使用して、同じ型のパンを入力および出力するようにしている。
これにより、入力と出力の型の一貫性が確保される。
BreadTypeはパンの種類を表す型(例えばBaguetteやBagel)であり、関数を呼び出す際に具体的な型を指定する。
関数に柔軟性を持たせるために型を指定するのはTypeScriptでもよくやる手法。JavaでもArrayを扱うときに、どの型の配列にするか変数宣言タイミングで指定したりする。そのようなやり方がMoveでも使われている。
(ちなみに私がJavaを書いていたのは1.6の時代なので、今はどうなってるか責任あんまり持てません)
typescriptpublic fun bake<BreadType: drop>
この制約は、BreadTypeがdrop能力を持つ必要があることを意味している。この制約を追加することで、型が適切な能力を持っていない場合はコンパイラがそのトランザクションを実行しないという安全性が追加されるらしい。
ファントム型
ジェネリクス型の一種。
「型入力が構造体で記録される場合でも、その入力型を実際にフィールドで使う必要がないというもの。このような場合、その型は単なる情報的な役割を果たし、構造体のフィールドには影響を与えない。ファントム型を使用することで、構造体同士を区別したり、特定の制約を強制したりできる。」
らしい、全く意味がわからない。習うよりなれろ。
typescriptmodule bakery::bread { // ファントム型を定義 struct Bread<phantom BreadType> has key, store { id: UID, baked: bool, weight: u64, // "BreadType"フィールドは存在しない! } // さまざまな種類のパン struct Baguette has drop {} struct Bagel has drop {} struct Focaccia has drop {} // 生のパンを作るジェネリック関数 public fun prepare<BreadType: drop>( weight: u64, breadType: BreadType, ctx: &mut TxContext ): Bread<BreadType> { return Bread<BreadType> { id: object::new(ctx), baked: false, weight: weight }; } // パンを焼くジェネリック関数 public fun bake<BreadType: drop>( bread: &mut Bread<BreadType> ): Bread<BreadType> { bread.baked = true; return bread; } }
この例では、Breadという構造体が<BreadType>という型を受け取っていルガ、その型はフィールドとして使用されていない。
通常、型入力を使う場合はフィールドで利用されることが期待されるが、ファントム型を使うことでこのルールを回避し、その型が情報的な役割のみを果たすことを示している。
[メリット]
- Bread<Baguette>、Bread<Bagel>、Bread<Focaccia>といった異なる種類のパンを同じBread構造体で表現できるが、型の違いを強制して区別することができる
- フィールドとしてその型を使わない場合でも、型レベルでの違いを保持できる
正直、あまりまだ使い所を理解していない。使ってるうちに手に馴染んでくる感じかな、と思う。
最後に
Java臭がするSolidityに比べ、ひたすらにRustっぽい。そういう意味でも次世代の言語なんだろう。
いずれArbitrum Stylusでも使用可能になるのだろうか。
次の次のバブル(2029か2030年ごろ?)には立派なアルトチェーン上位になってるかもしれない。