解答9: エラーハンドリングの完全理解
概要
この解答では、Zigのエラーハンドリングシステムについて学びます。Zigは例外を使用せず、エラーを戻り値として明示的に扱います。
Part 1: エラーセット
const std = @import("std");
const FileError = error{
NotFound,
PermissionDenied,
IoError,
};
const ParseError = error{
InvalidFormat,
UnexpectedToken,
EndOfInput,
};
// エラーセットの合成
const ConfigError = FileError || ParseError;
fn readFile(path: []const u8) FileError![]const u8 {
if (std.mem.eql(u8, path, "missing.txt")) {
return FileError.NotFound;
}
return "file content";
}
Part 2: try と catch
const std = @import("std");
const ComputeError = error{
DivisionByZero,
Overflow,
};
fn divide(a: i32, b: i32) ComputeError!i32 {
if (b == 0) return ComputeError.DivisionByZero;
return @divTrunc(a, b);
}
fn safeCompute(a: i32, b: i32) i32 {
// catch でデフォルト値
return divide(a, b) catch 0;
}
fn chainedComputation(values: []const i32) ComputeError!i32 {
var result: i32 = 0;
for (values) |v| {
result = try divide(result + v, 2);
}
return result;
}
Part 3: errdefer
const std = @import("std");
fn createResources(allocator: std.mem.Allocator) !struct { r1: []u8, r2: []u8 } {
var r1 = try allocator.alloc(u8, 100);
errdefer allocator.free(r1);
var r2 = try allocator.alloc(u8, 200);
errdefer allocator.free(r2);
return .{ .r1 = r1, .r2 = r2 };
}
ポイント解説
- 明示性: すべてのエラーは型システムで表現
- ゼロコスト: 正常パスにオーバーヘッドなし
- コンパイル時チェック: エラーの未処理をコンパイルエラーに
よくある間違い
// 危険!エラーを無視
_ = mightFail() catch {};
// 適切: エラーをログ
mightFail() catch |err| {
std.debug.print("Error: {}\n", .{err});
};