Now and Nawoo

NFTの制作記録、技術メモ → C#, Solidity, Blockchain, Bitcoin, Ethereum, NFT

Solidityメモ: structの初期化

struct(構造体)の初期化でちょっとハマったのでメモ。 (Solidity 0.8.10でテストしています。)

Solidity documentation - Types - Structs

structのメンバにstringの配列がある場合 (基本の例)

まず簡単な例として、Itemというstructを定義しました。メンバはstring型のnameとstringの配列のdataを持ちます。

add1,add2,add3という関数を作り、いずれもItemを作成して配列(items)に追加しています。 メンバにstringの配列があるので、これをどう初期化するかがポイントです。

  • add1 : dataに長さ0の配列を指定してItemを作成しています
  • add2 : dataは指定していませんが、空配列で初期化されます
  • add3 : 先にarrayにpush()してからnameを指定します (これもdataは空配列になります)
struct Item {
    string name;
    string[] data;
}

Item[] public items;

function add1() public {
    items.push(Item("add1", new string[](0)));
}
function add2() public {
    Item memory item;
    item.name = "add2";
    items.push(item);
}
function add3() public {
    Item storage item = items.push(); // ※
    item.name = "add3";
}

(※配列にはpush(x)とpush()のメソッドがありますが、push()の戻り値は新しく追加した要素になります。)

structを作成するにもいろいろな方法があります。と、まあ、ここまでは、いいのですが、、、。

structのメンバにstructの配列がある場合

Childというstructを定義して、stringのかわりにChildの配列をメンバにしてみます。

string[]からChild[]に変えただけですが、add1とadd2がコンパイルエラーになりました。

やっかいなことにVSCodeで書いている時にはエラーとならず、コンパイルしてはじめてエラー表示されます。 エラーメッセージに「not yet supported」とあるので、まだ実装されてないけど、そのうち実装される機能ということでしょうか。

add3のように直接storageで作成すればいけるようです。

struct Child {
    int256 id;
    string name;
}
struct Item {
    string text;
    Child[] data; // ← string[] data; から変更
}

Item[] public items;

function add1() public {
   // ↓コンパイルエラー  UnimplementedFeatureError: Copying of type struct Child memory[] memory to storage not yet supported.
    items.push(Item("add1", new Child[](0)));
}
function add2() public {
   // ↓コンパイルエラー  UnimplementedFeatureError: Copying of type struct Child memory[] memory to storage not yet supported.
    Item memory item;
    item.text = "add2";
    items.push(item);
}
function add3() public {
   // これはOK
    Item storage item = items.push();
    item.text = "add3";
}

structのメンバにmappingがある場合

次は配列ではなくmappingを含む場合ですが、こちらも add1とadd2がエラーになります。 今回はVSCodeで書いているときにもエラー表示が出ます。 mappingを含むstructは、memoryとして作成できないようです。

実は、add1もadd2もSolidityの古いバージョンではエラーにならなかったのですが、 Solidity 0.7.0以後はエラーが出るようになりました。 (そのため、古い記事にはadd1やadd2のような書き方をしているものもあります。)

mappingを含む場合も add3なら問題ありません。

struct Item {
    string text;
    mapping(uint256 => uint256) data;
}

Item[] public items;

function add1() public {
   // ↓エラー Struct containing a (nested) mapping cannot be constructed.
    items.push(Item("add1"));
}
function add2() public {
   // ↓エラー Type struct Item is only valid in storage because it contains a (nested) mapping.
    Item memory item;
    item.text = "add2";
    items.push(item);
}
function add3() public {
   // これはOK
    Item storage item = items.push();
    item.text = "add3";
}

おわりに

Solidityややこしいですね😖

内容に間違いや、もっとこう書けるよという点がありましたら、ぜひ教えてください。