Abort(中止)とAssert
return
とabort
は実行を終了する2つの制御フロー構造です。ひとつは現在の関数用、もうひとつはトランザクション全体用です。
abort
(中止)
abort
はひとつの引数:u64
型の中止コードを取る式です。例:
abort 42
このabort
式は、現在の関数の実行を中止し、 現在のトランザクションがグローバル状態へ加えた全ての変更を元へ戻します。abort
を「キャッチ」したり、その他の方法で処理するメカニズムはありません。
幸いMoveではトランザクションは全てかゼロかです。 つまりグローバルストレージへの変更は、トランザクションが成功した場合のみ一斉に行われます。このトランザクションによる変更のコミットメントにより、中止後に変更を取り消す心配はありません。このアプローチは柔軟性に欠けますが、非常にシンプルで予測可能です。
return
と同様、abort
は何らかの条件を満たせない時、制御フローを終了するのに役立ちます。
この例では、関数はベクターから2つの項目をポップしますが、ベクターに2つの項目がない場合は早期中止します。
use std::vector;
fun pop_twice<T>(v: &mut vector<T>): (T, T) {
if (vector::length(v) < 2) abort 42;
(vector::pop_back(v), vector::pop_back(v))
}
これは制御フロー構造の奥深くでさらに役立ちます。例えば、この関数はベクター内の全ての数値が指定したbound
より小さいかチェックします。そうでない場合は中止します。
use std::vector;
fun check_vec(v: &vector<u64>, bound: u64) {
let i = 0;
let n = vector::length(v);
while (i < n) {
let cur = *vector::borrow(v, i);
if (cur > bound) abort 42;
i = i + 1;
}
}
assert
assert
は、Moveコンパイラが提供する組み込みのマクロのような操作です。これはbool
型のコンディションとu64
型のコードの2つの引数を取ります。
assert!(condition: bool, code: u64)
操作はマクロなので!
で呼び出す必要があります。これはassert
の引数が「式による呼び出し」である事を伝えるためです。言い換えるとassert
は通常の関数ではなく、バイトコードレベルには存在しません。これはコンパイラー内で以下へ置き換えられます。
if (condition) () else abort code
assert
はただabort
自身で中止
するよりも一般的に使われます。上記のabort
例はassert
を使って書き直す事が出来ます。
use std::vector;
fun pop_twice<T>(v: &mut vector<T>): (T, T) {
assert!(vector::length(v) >= 2, 42); // 現在'assert'を使っています。
(vector::pop_back(v), vector::pop_back(v))
}
そして
use std::vector;
fun check_vec(v: &vector<u64>, bound: u64) {
let i = 0;
let n = vector::length(v);
while (i < n) {
let cur = *vector::borrow(v, i);
assert!(cur <= bound, 42); // 現在'assert'を使っています。
i = i + 1;
}
}
注意: 演算はif-else
へ置き換えられるためcode
の引数が必ずしも評価されるわけではありません。例:
assert!(true, 1 / 0)
算術エラーにはなりませんが、これは以下の式と同等です。
if (true) () else (1 / 0)
従って、算術式は評価されません。
Move VMのコードを中止する
abort
を使用する場合、VMがどの様にu64
コードを使用するのか理解する事が重要です。
通常、実行が成功すると、Move VMはグローバルストレージへ加えられた変更 (追加された/削除されたリソース、既存の リソースの更新等)の「変更セット」を生成します。
abort
へ達した場合、VMはかわりにエラーを表示します。そのエラーには、以下の2つの情報が含まれます。
- 中止を生成したモジュール(アドレスと名前)
- 中止コード。
例えば
address 0x2 {
module example {
public fun aborts() {
abort 42
}
}
}
script {
fun always_aborts() {
0x2::example::aborts()
}
}
上記のalways_aborts
スクリプトのようなトランザクションが0x2::example::aborts
を呼び出すと、VMは0x2::example
モジュールと42
コードを示すエラーを生成します。
これは、モジュール内で複数の 中止をグループ化する場合便利です。
この例では、モジュールには複数の関数で使用される2つの別々のエラーコードが有ります。
address 0x42 {
module example {
use std::vector;
const EMPTY_VECTOR: u64 = 0;
const INDEX_OUT_OF_BOUNDS: u64 = 1;
// iをjへ移動、jをkへ移動、kをiへ移動
public fun rotate_three<T>(v: &mut vector<T>, i: u64, j: u64, k: u64) {
let n = vector::length(v);
assert!(n > 0, EMPTY_VECTOR);
assert!(i < n, INDEX_OUT_OF_BOUNDS);
assert!(j < n, INDEX_OUT_OF_BOUNDS);
assert!(k < n, INDEX_OUT_OF_BOUNDS);
vector::swap(v, i, k);
vector::swap(v, j, k);
}
public fun remove_twice<T>(v: &mut vector<T>, i: u64, j: u64): (T, T) {
let n = vector::length(v);
assert!(n > 0, EMPTY_VECTOR);
assert!(i < n, INDEX_OUT_OF_BOUNDS);
assert!(j < n, INDEX_OUT_OF_BOUNDS);
assert!(i > j, INDEX_OUT_OF_BOUNDS);
(vector::remove<T>(v, i), vector::remove<T>(v, j))
}
}
}
abort
の型
abort i
式には任意の型を指定できます。これは、両方の構造が通常の制御フローから外れるため、その型の値を評価する必要がないためです。
以下は役に立ちませんが、型チェックには使えます。
let y: address = abort 0;
この動作は、全ての分岐ではなく一部の分岐で値を生成する分岐命令がある場合役立ちます。例えば:
let b =
if (x == 0) false
else if (x == 1) true
else abort 42;
// ^^^^^^^^ `abort42`の型は`bool`