pragma solidity ^0.4.23; // この文書の対象バージョン
はじめに
Ethereum上のコントラクトを書こうとしてSolidityを使ってみましたが、いろいろハマってしまいました。特に、配列の扱いについて複雑な面が多いと感じました。
文法の違い
Solidityは概ねC言語やJavaScript的な文法となっていますが、微妙なところで違うので、同じ感覚で書いていると思わぬところでエラーとなってしまいます。
変数宣言
変数宣言を複数いっぺんに行うことが、現行のSolidityでは行えません。「コントラクトの読みやすさに寄与するものではない」ということで、Issueも閉じられています。1つずつ書くほかありません。
uint a, b; // エラー
uint a;
uint b;
関数の修飾子
他の言語と同じ感覚でpublic function
と書いてみたところ、これもエラーとなりました。Solidityの関数記法は、function(引数) 修飾子 returning(返り値)
のように順番が決まっていて、そのとおりに書くしかありません。
storage
変数とmemory
変数
Ethereumがブロックチェーンの仕組みなので、変数にもコントラクトを超えて永続するstorage
変数と、1回の実行で役割を終えるmemory
変数があります。状況によってデフォルトでどちらになるかが決まっていますが、
- 関数外の状態変数…
storage
固定 - 関数の引数…デフォルトで
memory
- ローカル変数…デフォルトで
storage
というようになっています。ただ、煩雑ですので、状態変数以外は常に明示したほうがいいかもしれません。そして、それぞれの間で代入を行った場合、以下のような動作になります。配列などの参照型といっても、コピーされる場面が多々あるのは要注意です。
from\to | 状態 | memory |
storage |
---|---|---|---|
状態変数 | コピー | コピー | 参照設定 |
memory 変数 |
コピー | ☆ | コピー※ |
ローカルのstorage 変数 |
コピー | コピー※ | 参照のコピー※ |
☆:値型ならコピー、参照型(配列・struct
)なら参照のコピー
※:事前に参照設定をしていなければ動かない。
配列の複雑さ
さらに、配列では特有の注意点がいくつもあります。
添字の型
Solidityでは、配列の要素は0から始まり、「負の値でアクセスすると末尾から数える」というような機能がないこともあって、添字の型はuint
となっています。C言語的にfor(int i = 0; i < arr.length;++i){ arr[i] }
のように書いてしまうと、int
をuint
に暗黙の変換ができないので、コンパイル時点でエラーとなります。ループカウンタはuint
で宣言しましょう。
固定長配列と動的長さの配列1
Solidityの配列には、固定長のものと動的長さのものがありますが、動的長さの配列はmemory
とstorage
で挙動が異なります。
- 固定長配列…
int[3] arr;
のように、宣言時に個数を決め打つ。配列リテラルも固定長配列扱い。 -
memory
の動的長さの配列…int[] memory arr = new int[](5);
のように、new
で長さを決める。一度new
したあとには、長さを変更できない。 -
storage
の可変長配列…デフォルトでは要素0個の配列。.push
や.length
の操作で長さを変更可能。
なお、現行のSolidityでは、動的長さの配列に(配列リテラルを含め)固定長配列を代入できないので、「最初にリテラルを追加してから.push
していく」ようなことは行なえません。また、memory
配列は後から長さを変更できないので、「余分目に確保しておく」あるいは「必要な長さを事前に算出する」ような操作が必要です。
ハッシュテーブル(mapping
)
連想配列的に使えるmapping
ですが、中身はハッシュテーブルになっていて2、元のキーの値は記憶していません(いくつ入っているか知ることも不可能です)。また、事前に設定していなかったキーで取得しようとした場合、ゼロクリアされた値が得られます。storage
専用です。