Zig 入門 #8 - Errors
2022-05-06 / Zig, Programming
ziglearn.org を参考に Zig
の基本を一通りさらってみます。今回は Chapter 1 - Basics | ziglearn.org からErrors
について。
Errors
参考: Errors
Zig
のエラーの扱いは特殊だなぁという印象です。
Error Set
Zig でエラーを定義するには Error Set
を定義します。Enum のような構文で記述します。
const FileOpenError = error{
AccessDenied,
OutOfMemory,
FileNotFound,
};
ある Error Set
が別の Error Set
のサブセット(要素が同じか一部分)であるときは、スーパーセットと合わせて扱うことができます。
const AllocationError = error{OutOfMemory};
// FileOpenError ⊇ AllocationError として扱われる
const err: FileOpenError = AllocationError.OutOfMemory;
// どちらの `OutOfMemory` も同じ
std.log.info("Same error: {}", .{err == FileOpenError.OutOfMemory});
std.log.info("Same error: {}", .{FileOpenError.OutOfMemory == AllocationError.OutOfMemory});
マージ
Error Set
はマージすることができます。
const A = error{ NotDir, PathNotFound };
const B = error{ OutOfMemory, PathNotFound };
const C = A || B;
anyerror
anyerror
は全てのエラーセットのスーパーセットになるエラーセットになります。この型を通常使用するのは避けた方が良いようです。
Error Union Type
!
を利用して Error Set
と他の型を合わせた Error Union Type
を定義することができます。関数型言語は詳しくないのですが Either
のようなものでしょうか。
// AllocationError か u16 のどちらかという型(error union型)
var mayby_error: AllocationError!u16 = 10;
関数の戻り値も Error Union Type で定義することができます。
// 関数の戻値を error union として定義できる
fn failingFunction() error{Oops}!void {
// error しか返さないので !void となる
return error.Oops;
}
Catch
Error Union Type
はそのままではエラーか値か不明なので catch
を利用して値を取得する必要があります。
// errorとのユニオン型は catch を用いて unwrap する
// catch の後にエラーだった際の値を指定する
const no_error = mayby_error catch 0;
// エラーではないので 10
std.log.info("no_error: {}", .{no_error});
// errorを代入する
mayby_error = AllocationError.OutOfMemory;
const catched_error = mayby_error catch 100;
// エラーだったので catch で指定された 100
std.log.info("no_error: {}", .{catched_error});
Payload Capturing
catch
の際にエラーを利用するためには payload capturing
を行います。 catch |err| ...
のように記述し err
の中にエラーが代入されます。payload capturing
で指定する変数が上位のスコープで定義されている場合には、それが使用されます。なので既存の var
で定義された再代入可能な変数である必要があります。未定義であれば、そのcatch
のみで利用可能なスコープの変数になります。
// err はすでに const で定義済みなので再代入できずコンパイルエラーになる
// _ = mayby_error catch |err| {
// std.log.info("error: {}", .{err});
// };
_ = mayby_error catch |captured_err| {
std.log.info("error: {}", .{captured_err});
// info: error: error.OutOfMemory
};
// captured_err は2回目だが catch でスコープが閉じているため再利用できる
_ = mayby_error catch |captured_err| {
std.log.info("error(2回目): {}", .{captured_err});
// info: error: error.OutOfMemory
};
catch
の後のブロック
catch |err|
の直後にはブロック {}
を記述することが可能ですが if
などと同等のブロックであるため return
を記述すると catch
が属している上位のブロックの制御がそこで返ることになります。
fn catchError() void {
failingFunction() catch |err| { // ← payload capturing
std.log.info("Oops: {}", .{err == error.Oops}); // true
// catch のブロックから return は関数自体の return になる
return;
};
// ここには到達しない
std.log.info("unreached", .{});
}
またブロックを利用しつつ値を返したい場合には catch label: { break :lable value }
という構文を利用します。いくつか GitHub の Issue など見ていたのですが、ラベルには blk
を用いられているようです。(簡略化して記述できるようになるとうれしいですが)
const returned_value = mayby_error catch |captured_err| blk: {
std.log.info("error(3回目): {}", .{captured_err});
break :blk 1000;
};
std.log.info("returned_value: {}", .{returned_value});
// info: returned_value: 1000
Try
try
は x catch |err| return err
の糖衣構文になります。
fn tryError() error{Oops}!i32 {
// try は catch したエラーをそのまま返す syntax sugar
// failingFunction() catch |err| { return err; };
try failingFunction();
// ↑ error.Oops を返しているのでここは到達しない
return 12;
}
もちろんエラーが無ければ通常の値を返します。
const t: anyerror!u8 = 10;
// try は xcath |err| return err なのでエラーで無ければ通常の値となる
const v = try t;
std.log.info("v: {}", .{v});
// info: v: 10
Error Defer
errordefer
は defer
と似ていますがエラーが返されるときのみ実行されます。
var problem: u32 = 98;
// ! の前の error の型は返される型から推論される
fn errorDefer() !u32 {
errdefer problem += 1;
try failingFunction();
// ↑ error.Oops を返しているのでここは到達しない
return 1;
}
_ = errorDefer() catch blk: {
// errodefer で +1 されているので 99 になる
std.log.info("problem: {}", .{problem});
break :blk 100;
};
今回使用したコードはこちらです。