Zenにはエラー専用で使用するエラー型が存在します。プログラマは、関数の返り値型がエラー型を返すかどうかを見るだけで、その関数の処理が失敗する可能性があるかどうか、を認識できます。
Zenコンパイラは、このエラー専用型に対して様々な検査を行い、エラーを適切に処理するようにプログラマを支援します。
エラー型の定義は、列挙型と似ています。const 型名 = error { エラー種別, ... };
でエラー型を定義します。
文字列から整数値に変換する処理を考えてみます。例えば、文字列"1234"を入力すると、u32
型の1234
に変換します。考えられるエラーは、次のものがあります。
u32
の範囲を超えるこれを表現するエラー型は、次のように定義できます。
examples/ch03-user-defined-types/src/error.zen:4:8
const IntParserError = error{
InvalidInputString,
StringIsEmpty,
ValueOutOfRange,
};
定義したエラー型は、次に説明するエラー共用体という形で関数の戻り値型とします。
エラー共用体は、エラー型と通常の型 (u32
など) とを1つの型として扱えるようにします。エラー共用体は!
演算子を使い、エラー型!T
(T
は任意の型) で宣言します。エラー共用体は、実用上は関数の戻り値型として利用することがほとんどです。
さきほど定義したIntParserError
とu32
とのエラー共用体を返す関数は、次のように定義できます。
fn parseU32(str: []u8) IntParserError!u32 {
// ...
};
エラー共用体は、ある時には1つのヴァリアントとして振る舞うので、parseU32
関数はIntParserError
かu32
を返す関数である、と関数シグネチャから判断できます。このようにエラー共用体を戻り値型として持つ関数は、内部の処理が失敗する可能性があることを教えてくれます。
parseU32
関数の実装は、次の通りです。文字列を1桁ずつ順番に読み取り、数値に変換します。
examples/ch03-user-defined-types/src/error.zen:10:33
fn parseU32(str: []u8) IntParserError!u32 {
if (str.len == 0) {
return IntParserError.StringIsEmpty;
}
var result: u32 = 0;
for (str) |c| {
const digit = switch (c) {
'0'...'9' => c - '0',
else => return IntParserError.InvalidInputString,
};
// result *= 10
if (@mulWithOverflow(u32, result, 10, &mut result)) {
return IntParserError.ValueOutOfRange;
}
// result += digit
if (@addWithOverflow(u32, result, digit, &mut result)) {
return IntParserError.ValueOutOfRange;
}
}
return result;
}
IntParserError
(関数の上から4番目までのreturn
) かu32
(関数の最後のreturn
) の値を返していることがわかります。それぞれのIntParserError
とu32
は、IntParserError!u32
のエラー共用体型へ、暗黙の型変換が行われています。
エラー型!T
のエラー共用体に格納されたエラー型もしくはT
の値を使うためには、エラー共用体からアンラップしなければなりません。エラー共用体をアンラップする方法を説明します。
if
の条件式でエラー共用体インスタンスがエラー型かどうかを検査した上で、アンラップすることができます。if (エラー共用体インスタンス) |Tの値| { エラーでない時の処理 } else |エラー型の値 | { エラー処理 }
という構文を使います。else
でエラー型を必ず処理しなければならないことに注意して下さい。
examples/ch03-user-defined-types/src/error.zen:35:51
test "unwrap with if" {
// `u32`が格納されている場合
var result: IntParserError!u32 = 42;
if (result) |value| {
ok(value == 42);
} else |err| {
// do nothing
}
// `IntParserError`が格納されている場合
result = IntParserError.ValueOutOfRange;
if (result) |value| {
// do nothing
} else |err| {
ok(err == IntParserError.ValueOutOfRange);
}
}
if
と同様に、while
にもエラー共用体インスタンスがエラー型かどうか検査した上で、アンラップする構文が用意されています。while (エラー共用体インスタンス) |Tの値| { エラーでない時の処理 } else |エラー型の値 | { エラー処理 }
という構文になります。
エラー共用体に利用できる専用の演算子として、catch
演算子が用意されています。catch
演算子は、2つの構文を提供しています。
エラー共用体インスタンスの型がエラー型!T
の場合、式はT
の値もしくはnoreturn
型である必要があります。
1つ目の構文では、式はT
のデフォルト値を与える利用方法が多いです。
examples/ch03-user-defined-types/src/error.zen:53:60
test "unwrap with catch" {
var result = parseU32("1234") catch 0;
ok(@TypeOf(result) == u32);
ok(result == 1234);
result = parseU32("invalid") catch 0;
ok(result == 0);
}
parseU32
の戻り値がエラー型の時のみ、デフォルト値として0
が格納されます。result
の型はIntParserError!u32
をアンラップしたu32
となります。
式の部分は、noreturn
型も許されるため、次のように、return
やunreachable
を書くこともできます。
examples/ch03-user-defined-types/src/error.zen:62:64
// エラーの場合、関数から戻る
test "expression of catch allows return" {
var result = parseU32("invalid") catch return;
}
エラー共用体インスタンスが、絶対にエラー型にならないことが明らかな場合、catch unreachable
とすることができます。
examples/ch03-user-defined-types/src/error.zen:66:68
test "result will never be an error" {
var result = parseU32("1234") catch unreachable;
}
エラー型が格納されている場合に、デフォルト値を与える以外の処理がしたい場合、2つ目の構文を使います。
examples/ch03-user-defined-types/src/error.zen:70:76
test "handle error with catch" {
var result = parseU32("invalid") catch |err| {
std.debug.warn("display error: {}\n", .{err});
std.debug.warn("abort!\n", .{});
return;
};
}
display error: error.InvalidInputString
abort!
catch |err| { ... }
の{}
ブロックは、型T
かnoreturn
型でなければならないことに注意して下さい。
エラー発生時の処理を、上位関数にそのまま伝播したい場合があります。その場合、try
を使うことができます。構文はtry エラー共用体インスタンス;
です。
examples/ch03-user-defined-types/src/error.zen:78:87
fn tryParseU32(str: []u8) IntParserError!void {
var result = try parseU32(str);
// `result` (u32) を使って何かやる
}
test "try" {
var result = tryParseU32("invalid") catch |err| {
ok(err == IntParserError.InvalidInputString);
};
}
try エラー共用体インスタンス;
はエラー共用体インスタンス catch |err| return err;
のシンタックスシュガーです。
エラー型には、2つの暗黙の型変換が存在します。エラー型同士の型変換と、エラー型からエラー共用体への型変換です。
エラー型同士の暗黙の型変換は、サブセットからスーパーセットに変換する場合のみ、発生します。
例えば、次のSupersetError
とSubsetError
があるとします。
examples/ch03-user-defined-types/src/error_convert.zen:4:13
const SupersetError = error{
A,
B,
C,
};
const SubsetError = error{
A,
B,
};
このようにスーパーセットとサブセットの関係にあるエラー型は、サブセットからスーパーセットへの暗黙の型変換が可能です。
examples/ch03-user-defined-types/src/error_convert.zen:15:18
test "implicit cast from subset to superset" {
const err: SupersetError = SubsetError.A;
ok(err == SupersetError.A);
}
次の、スーパーセットからサブセットへの変換は、コンパイルエラーになります。
const err: SubsetError = SupersetError.A;
次のNotSubsetError
も、SupersetError
のサブセットになっていません。
const NotSubsetError = error{
A,
D,
};
そのため、次のコードはコンパイルエラーになります。
const err: SupersetError = NotSubsetError.A;
error[E02046]: expected 'SupersetError', found 'NotSubsetError'
const err: SupersetError = NotSubsetError.A;
~
note[E00021]: 'error.A' not a member of destination error set 'SupersetError'
A,
~
エラー種別を1つだけ持つエラー型の値を生成する場合、専用のシンタックスシュガーが用意されています。error.エラー種別
という構文で、error { エラー種別 }.エラー種別
と等価になります。
examples/ch03-user-defined-types/src/error_convert.zen:25:29
test "syntax sugar for error declaration" {
const err1 = error.AnError;
const err2 = error{AnError}.AnError;
ok(err1 == err2);
}
このシンタックスシュガーと、エラー型がスーパーセットへの暗黙の型変換を許すこと、とを組み合わることで、エラー型.エラー種別
の代わりに、error.エラー種別
の省略記法が使用できます。
examples/ch03-user-defined-types/src/error_convert.zen:31:39
fn implicitConversion() IntParserError!void {
return error.InvalidInputString;
}
test "implicit conversion from syntax sugar" {
implicitConversion() catch |err| {
ok(err == IntParserError.InvalidInputString);
};
}
switch
でエラー型のエラー種別ごとのハンドラを書くことができます。次のコードでは、エラー種別ごとにことなる警告メッセージを出力しています。
examples/ch03-user-defined-types/src/error.zen:105:117
fn handlerIntParserError(err: IntParserError) void {
switch (err) {
error.InvalidInputString => {
std.debug.warn("valid input characters are [0-9]\n", .{});
},
error.StringIsEmpty => {
std.debug.warn("string must have one or more length\n", .{});
},
error.ValueOutOfRange => {
std.debug.warn("valid value range is between 0 to 2^32\n", .{});
},
}
}
任意のエラー型をマージすることができます。エラー型のマージには||
演算子を使用します。IntParserError
とKeywordParserError
をマージして、ParserError
を定義します。
examples/ch03-user-defined-types/src/error_convert.zen:41:52
const IntParserError = error{
InvalidInputString,
StringIsEmpty,
ValueOutOfRange,
};
const KeywordParserError = error{
NoSuchKeyWord,
StringIsEmpty,
};
const ParserError = IntParserError || KeywordParserError;
ParserError
は2つのエラー型のエラー種別を合わせたエラー型になります。
examples/ch03-user-defined-types/src/error_convert.zen:54:64
test "merge error sets" {
const err: ParserError = error.StringIsEmpty;
switch (err) {
error.InvalidInputString,
error.ValueOutOfRange,
error.NoSuchKeyWord,
=> ok(false),
error.StringIsEmpty,
=> ok(true),
}
}
関数の戻り値型におけるエラー型を推論させることができます。エラー型を推論させる場合、!T
でエラー共用体の型を宣言します。
examples/ch03-user-defined-types/src/error_convert.zen:66:68
fn inferredError() !void {
return error.StringIsEmpty;
}
inferredError
関数の戻り値型は、!T
です。この関数は戻り値型がジェネリックな関数になります。
examples/ch03-user-defined-types/src/error_convert.zen:70:81
test "error type inference" {
inferredError() catch |err| {
ok(err == IntParserError.StringIsEmpty);
};
inferredError() catch |err| {
ok(err == KeywordParserError.StringIsEmpty);
};
inferredError() catch |err| {
ok(err == ParserError.StringIsEmpty);
};
}
関数の取り扱いがトリッキーになるため、可能な場合は、明示的にエラー型を記述することをお勧めします。
anyerror
はコンパイル時に判明する全てのエラー型を内包するエラー型です。anyerror
は全てのエラー型のスーパーセットです。そのため、全てのエラー型は、anyerror
に暗黙の型変換が可能です。
☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2020 connectFree Corporation. All rights reserved. | 特定商取引法に基づく表示
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.