ざっくりZig - エラー(error, try, catch, if...else, ビルトイン関数)
Zigのmain
関数を書くとき、戻り値はvoid
やu8
になるのですが、実際には!void
や!u8
にすることのほうが多いかもしれません。この!
は関数の処理中にエラーが発生する可能性があることを表しています。また、関数を実行するときtry
を先頭に書くことがあります。これも関数を実行するとエラーが発生する可能性があることを表しています。ここではZig行うエラー処理を紹介していきます。
エラーの実体
Zigではエラーを表すものとしてerror
, .....Error
, anyerror
, @compileError
があります。それぞれの意味は以下の通りです。
-
error
-error.OutOfMemory
やerror.InvalidCharacter
など個別のエラー -
.....Error
-std.fmt.ParseIntError
やstd.fs.File.OpenError
など複数のerror
を特定の分類のもと集めて定義されたエラーセット型で、関数の戻り値で利用される -
anyerror
- すべてのエラーを表す型で、関数の戻り値でanyerror!u8
などとするときに利用される
エラーの定義
エラーはerror.Something
のようにerror.
に名称を続けて定義します。関数内でエラーが起きた時にreturn error.Something;
を実行し、呼び出し元にエラーが起きたことを知らせることができます。その場合、関数の戻り値の型名はerror{Something}
となり、実行時エラーを表します。
fn f() error{Something} {
return error.Something;
}
関数からエラーを返す
関数が実行されると、正常に処理されたらu8
などの戻り値を返しますが、処理中にエラーが起きて処理が終了することもあります。その場合は関数の戻り値をerror{Something}!u8
のように定義します。これをエラー共用型(Error Union Type)といいます。エラーの型を示さず!u8
のように示すこともできます。
fn f(n: u8) error{Something}!u8 {
if (n == 0) return n;
return error.Something;
}
こうした関数の戻り値を出力するとき{!}
を使うと正常に処理された場合はその値、そうでなければエラーが出力されます。
try stdout.print("f(0) = {!}\n", .{f(0)});
try stdout.print("f(1) = {!}\n", .{f(1)});
// 結果
f(0) = 0
f(1) = error.Something
エラーセット型
エラーセット型は複数のエラーを1つにまとめて新たな型とするものです。以下のようにerror {...}
で複数のエラーをまとめて代入します。
// error.InvalidValue, error.OutOfMemoryを持つエラーセット型AppErrorを定義
const AppError = error{
InvalidValue,
OutOfMemory
};
エラーセット型は複数を結合して新たなエラーセット型を作成できます。
const AppError = error{
InvalidValue,
OutOfMemory
};
const FileError = error{
NotFound,
DeviceBusy
};
const AccessError = AppError || FileError; // エラーセット型の結合
エラーセット型も関数の戻り値の宣言に組み込んでエラー共用型にできます。
fn f(n: usize) AppError!usize {
if (n == 1) return error.InvalidValue;
if (n == 2) return error.OutOfMemory;
return n;
}
エラー処理
関数を実行してエラーが起きた時、try
, catch
, if...else
のいずれかで処理できます。
try
try
はエラーでないときは結果を返し、エラーの時はエラーを出力して処理を中止します。関数の戻り値はエラー共用型とします。
_ = try f(0); // エラーではないので結果を返す
_ = try f(1); // エラーで中止
catch
catch
はエラーが起きた後の処理を定義します。起きたエラーの内容を取得できますので、switch
でエラーの内容ごとに処理を振り分けられます。また、catch
の後にreturn
, break
, continue
などを続けることもできます。
// エラーではないのでcatch以降は実行されない
_ = f(0) catch |err| ...;
// errでerror.....を取得し、処理に利用できる
_ = f(1) catch |err| try stdout.print("f({}) catch {}\n", .{ n, err });
// 結果
f(1) catch error.InvalidValue
f(2) catch error.OutOfMemory
for (0..3) |n| {
_ = f(n) catch |err| switch (err) {
error.InvalidValue => try stderr.print("f({}) 値が間違っています\n", .{n}),
error.OutOfMemory => try stderr.print("f({}) メモリが足りません\n", .{n}),
};
}
// 結果 - f(0)は正常に処理されるのでメッセージは出力されない
f(1) 値が間違っています
f(2) メモリが足りません
if...else
if...else
は関数から正常に値が戻されたときはif ... |r| {...}
の方の処理を実行します。r
は関数から戻された値を表します。エラーが起きた時はelse |err| {...}
の方の処理を実行します。err
は関数から戻されたエラーを表します。
for (0..3) |n| {
if (f(n)) |r| {
// 値が返ってきたときの処理
try stdout.print("f({}) = {}\n", .{ n, r });
} else |err| {
// エラーが起きた時の処理
try stderr.print("f({}) ", .{n});
switch (err) {
error.InvalidValue => try stderr.print("値が間違っています\n", .{}),
error.OutOfMemory => try stderr.print("メモリが足りません\n", .{}),
}
}
}
// 結果
f(0) = 0
f(1) 値が間違っています
f(2) メモリが足りません
エラーに関連するビルトイン関数
エラーに関連するビルトイン関数(Builtin Functions)は
関数 | 引数 | 戻り値 | 処理 |
---|---|---|---|
@intFromError |
エラー | 符号なし整数 | エラーから数値を取得 |
@errorFromInt |
符号なし整数 | エラー | 数値からエラーを取得 |
@errorName |
エラー |
error. の後に続く名称 |
エラーから名称を取得 |
@panic |
メッセージ | 処理停止(noreturn) | panic状態で終了 |
@compileError |
メッセージ | 処理停止(noreturn) | コンパイルエラー |
@errorReturnTrace [1]
|
なし | スタックトレース(?*std.builtin.StackTrace) | スタックとレースを取得 |
@errorCast [2]
|
変換前のエラー | 変換後のエラー | エラーの型を変換 |
@intFromError, @errorFromInt
@intFromError
関数はエラーに対し自動的に割り当てられた符号なし整数(16ビット)の値を取得します。@errorFromInt
関数は逆にその整数からエラーを取得します。
const err = error.Something;
const ife = @intFromError(err);
const efi = @errorFromInt(ife);
try stdout.print("@intFromError({}) = {}\n", .{ err, ife });
try stdout.print("@errorFromInt({}) = {}\n", .{ ife, efi });
// 結果(環境により異なる)
@intFromError(error.Something) = 19
@errorFromInt(19) = error.Something
@errorName
@errorName
関数はerror.Something
のSomething
の部分を文字列で取得します。
const err = error.Something;
try stdout.print("@errorName({}) = {s}\n", .{ err, @errorName(err) });
// 結果
@errorName(error.Something) = Something
@panic
@panic
関数は引数のメッセージを出力し、panic状態で処理を中止します。
fn f() void {
@panic("panic from f().");
}
pub fn main() !void {
f();
}
// 処理終了(番号は環境により異なる)
thread 17112 panic: panic from f().
.....
@compileError
@compileError
関数はコンパイルエラーを表します。コンパイル時点でこの関数に遭遇したら引数のメッセージを出力しコンパイル(zig build-exe
, zig run
, zig test
など)を中止します。バージョンアップにより廃止した処理、実装予定の処理、実行環境が未対応など、コンパイル不可であることを明示するときに使用します。
fn f() void {
@compileError("Not Implemented.");
}
pub fn main() !void {
f();
}
$ zig build-exe error_example.zig
error_example.zig:2:5: error: Not Implemented.
@compileError("Not Implemented.");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.....
まとめ
- エラーは
error.Something
のように定義し、関数内でreturn error.Something;
を実行すると呼び出し元にエラーが起きたことが知らされる - 関数がエラーを返す場合は戻り値の型名を
error{Something}
とするが、本来の戻り値の型が別にあるときはerror{Something}!u8
のようなエラー共用型(Error Union Type)で表す - エラー共用型を戻り値とする関数の結果を出力するとき
{!}
を使うと正常に処理された結果かエラーのどちらかが出力される - 複数のエラーを1つにまとめたエラーセット型(Error Set Type)を定義できる
- 2つのエラーセット型を結合してあらたなエラーセット型を定義できる
- エラー後の処理は
try
,catch
,if
で実行できる -
try
は関数から正常に値が戻されたときは処理を続行し、エラーの時は処理を中止する(関数の戻り値はエラー共用型) -
catch
はエラーを取得し、そのあとの処理を定義する -
if...else
は関数がエラーとなったときelse |err| {...}
の処理を行う - エラーに関連するビルトイン関数(
@intfromError
など)が定義されている
< 関数
関数の終了処理(defer, errdefer, unreachable, @panic) >
ざっくりZig 一覧
Discussion