課題9: エラー処理の実践
マンダトリー要件 (80点)
Part 1: ファイルシステムシミュレータ (20点)
エラー処理を使ったファイルシステムのシミュレーションを実装してください。
ファイル: part1/filesystem.zig
const std = @import("std");
const FileError = error{
FileNotFound,
PermissionDenied,
FileAlreadyExists,
DirectoryNotEmpty,
InvalidPath,
};
const File = struct {
name: []const u8,
content: []const u8,
read_only: bool,
fn create(name: []const u8, content: []const u8, read_only: bool) File {
return File{
.name = name,
.content = content,
.read_only = read_only,
};
}
};
const FileSystem = struct {
files: std.ArrayList(File),
allocator: std.mem.Allocator,
fn init(allocator: std.mem.Allocator) FileSystem {
return FileSystem{
.files = std.ArrayList(File).init(allocator),
.allocator = allocator,
};
}
fn deinit(self: *FileSystem) void {
self.files.deinit();
}
// TODO: ファイルを作成(既に存在する場合はエラー)
fn createFile(self: *FileSystem, name: []const u8, content: []const u8) FileError!void {
// 実装
}
// TODO: ファイルを読み込み(存在しない場合はエラー)
fn readFile(self: *FileSystem, name: []const u8) FileError![]const u8 {
// 実装
}
// TODO: ファイルを書き込み(読み取り専用の場合はエラー)
fn writeFile(self: *FileSystem, name: []const u8, content: []const u8) FileError!void {
// 実装
}
// TODO: ファイルを削除(存在しない場合はエラー)
fn deleteFile(self: *FileSystem, name: []const u8) FileError!void {
// 実装
}
// TODO: ファイルの存在確認
fn exists(self: *FileSystem, name: []const u8) bool {
// 実装
}
};
pub fn main() !void {
const allocator = std.heap.page_allocator;
var fs = FileSystem.init(allocator);
defer fs.deinit();
std.debug.print("=== File System Simulator ===\n\n", .{});
// ファイル作成
std.debug.print("Creating files...\n", .{});
try fs.createFile("test.txt", "Hello, World!");
try fs.createFile("readme.md", "# README");
// 重複作成(エラー)
fs.createFile("test.txt", "duplicate") catch |err| {
std.debug.print("Error creating duplicate: {}\n", .{err});
};
// ファイル読み込み
std.debug.print("\nReading files...\n", .{});
const content = try fs.readFile("test.txt");
std.debug.print("Content: {s}\n", .{content});
// 存在しないファイル(エラー)
fs.readFile("nonexistent.txt") catch |err| {
std.debug.print("Error reading nonexistent file: {}\n", .{err});
};
// ファイル書き込み
std.debug.print("\nWriting to file...\n", .{});
try fs.writeFile("test.txt", "Updated content");
// ファイル削除
std.debug.print("\nDeleting file...\n", .{});
try fs.deleteFile("test.txt");
// 削除後の読み込み(エラー)
fs.readFile("test.txt") catch |err| {
std.debug.print("Error reading deleted file: {}\n", .{err});
};
}
Part 2: リソース管理システム (20点)
errdeferを使った適切なリソース管理を実装してください。
ファイル: part2/resource_manager.zig
const std = @import("std");
const ResourceError = error{
AllocationFailed,
InitializationFailed,
TooManyResources,
};
const Resource = struct {
id: u32,
data: []u8,
allocator: std.mem.Allocator,
// TODO: リソースを作成(エラー時はメモリを解放)
fn create(allocator: std.mem.Allocator, id: u32, size: usize) ResourceError!Resource {
std.debug.print("Allocating resource {}...\n", .{id});
// メモリ確保
const data = allocator.alloc(u8, size) catch {
return ResourceError.AllocationFailed;
};
errdefer allocator.free(data); // エラー時に解放
// 初期化(失敗する可能性)
// TODO: 初期化処理を実装
std.debug.print("Resource {} created\n", .{id});
return Resource{
.id = id,
.data = data,
.allocator = allocator,
};
}
// TODO: リソースを破棄
fn destroy(self: *Resource) void {
std.debug.print("Destroying resource {}...\n", .{self.id});
self.allocator.free(self.data);
}
};
const ResourceManager = struct {
resources: std.ArrayList(Resource),
allocator: std.mem.Allocator,
max_resources: u32,
fn init(allocator: std.mem.Allocator, max_resources: u32) ResourceManager {
return ResourceManager{
.resources = std.ArrayList(Resource).init(allocator),
.allocator = allocator,
.max_resources = max_resources,
};
}
fn deinit(self: *ResourceManager) void {
for (self.resources.items) |*res| {
res.destroy();
}
self.resources.deinit();
}
// TODO: リソースを追加(複数のerrdeferを使用)
fn addResource(self: *ResourceManager, size: usize) ResourceError!void {
if (self.resources.items.len >= self.max_resources) {
return ResourceError.TooManyResources;
}
const id = @as(u32, @intCast(self.resources.items.len));
const resource = try Resource.create(self.allocator, id, size);
errdefer resource.destroy(); // エラー時に破棄
try self.resources.append(resource);
}
// TODO: すべてのリソースを解放
fn clear(self: *ResourceManager) void {
// 実装
}
};
pub fn main() !void {
const allocator = std.heap.page_allocator;
var manager = ResourceManager.init(allocator, 3);
defer manager.deinit();
std.debug.print("=== Resource Manager ===\n\n", .{});
// リソースを追加
std.debug.print("Adding resources...\n", .{});
try manager.addResource(1024);
try manager.addResource(2048);
try manager.addResource(512);
// 最大数を超える(エラー)
manager.addResource(256) catch |err| {
std.debug.print("Error: {}\n", .{err});
};
std.debug.print("\nResource count: {}\n", .{manager.resources.items.len});
}
Part 3: エラーチェーンとラッピング (20点)
複数レイヤーのエラー処理を実装してください。
ファイル: part3/error_chain.zig
const std = @import("std");
// 低レベルエラー
const StorageError = error{
DiskFull,
IOError,
CorruptedData,
};
// 中間レベルエラー
const DatabaseError = error{
ConnectionFailed,
QueryFailed,
TransactionFailed,
} || StorageError;
// 高レベルエラー
const ServiceError = error{
Unavailable,
InvalidRequest,
InternalError,
} || DatabaseError;
// TODO: 低レベル操作(ストレージ)
fn writeToStorage(data: []const u8) StorageError!void {
_ = data;
// シミュレート: ディスクが満杯
return StorageError.DiskFull;
}
// TODO: 中間レベル操作(データベース)
fn saveToDB(data: []const u8) DatabaseError!void {
writeToStorage(data) catch |err| {
std.debug.print("Storage error: {}\n", .{err});
// ストレージエラーをDBエラーにラップ
return DatabaseError.QueryFailed;
};
}
// TODO: 高レベル操作(サービス)
fn processRequest(data: []const u8) ServiceError!void {
saveToDB(data) catch |err| {
std.debug.print("Database error: {}\n", .{err});
// DBエラーをサービスエラーにラップ
return ServiceError.InternalError;
};
}
// TODO: エラーを詳細にログ記録
fn logError(err: anyerror, context: []const u8, level: u32) void {
const indent = " " ** (level * 2);
std.debug.print("{s}[Level {}] {s}: {}\n", .{indent, level, context, err});
}
// TODO: エラーチェーンをトレース
fn handleRequest(data: []const u8) void {
processRequest(data) catch |err| {
std.debug.print("\n=== Error Chain Trace ===\n", .{});
logError(err, "Service Layer", 0);
logError(err, "Database Layer", 1);
logError(err, "Storage Layer", 2);
};
}
pub fn main() void {
std.debug.print("=== Error Chain Example ===\n", .{});
handleRequest("test data");
}
Part 4: リトライとフォールバック (20点)
エラー発生時のリトライとフォールバック機能を実装してください。
ファイル: part4/retry.zig
const std = @import("std");
const NetworkError = error{
ConnectionTimeout,
ServerError,
ServiceUnavailable,
};
var attempt_counter: u32 = 0;
// TODO: 不安定なネットワーク操作(3回目で成功)
fn unreliableNetworkCall() NetworkError![]const u8 {
attempt_counter += 1;
std.debug.print("Attempt #{}\n", .{attempt_counter});
if (attempt_counter < 3) {
return NetworkError.ConnectionTimeout;
}
return "Success!";
}
// TODO: リトライロジックを実装
fn retryWithBackoff(
comptime T: type,
operation: fn() NetworkError!T,
max_retries: u32,
initial_delay_ms: u64,
) NetworkError!T {
var retry_count: u32 = 0;
var delay_ms = initial_delay_ms;
while (retry_count < max_retries) : (retry_count += 1) {
if (operation()) |result| {
return result;
} else |err| {
std.debug.print("Error: {}, retrying in {}ms...\n", .{err, delay_ms});
if (retry_count == max_retries - 1) {
return err;
}
// バックオフ(遅延を倍増)
std.time.sleep(delay_ms * std.time.ns_per_ms);
delay_ms *= 2;
}
}
return NetworkError.ServiceUnavailable;
}
// TODO: フォールバック機能
fn fetchWithFallback(primary_url: []const u8, fallback_url: []const u8) ![]const u8 {
_ = primary_url;
_ = fallback_url;
// プライマリを試行
attempt_counter = 0;
if (retryWithBackoff([]const u8, unreliableNetworkCall, 2, 100)) |result| {
std.debug.print("Primary succeeded\n", .{});
return result;
} else |_| {
std.debug.print("Primary failed, trying fallback...\n", .{});
}
// フォールバックを試行
attempt_counter = 0;
return retryWithBackoff([]const u8, unreliableNetworkCall, 3, 100);
}
pub fn main() !void {
std.debug.print("=== Retry with Backoff ===\n\n", .{});
attempt_counter = 0;
const result1 = try retryWithBackoff([]const u8, unreliableNetworkCall, 5, 100);
std.debug.print("Result: {s}\n\n", .{result1});
std.debug.print("=== Fallback Example ===\n\n", .{});
const result2 = try fetchWithFallback("http://primary.com", "http://backup.com");
std.debug.print("Final result: {s}\n", .{result2});
}
ボーナス課題 (20点)
Bonus 1: エラーコンテキストシステム (10点)
エラーに追加情報を付与するコンテキストシステムを実装してください。
ファイル: bonus1/error_context.zig
const std = @import("std");
const AppError = error{
ValidationFailed,
ProcessingFailed,
SystemError,
};
const ErrorContext = struct {
error_type: AppError,
message: []const u8,
file: []const u8,
line: u32,
timestamp: i64,
// TODO: エラーコンテキストを作成
fn create(
err: AppError,
message: []const u8,
file: []const u8,
line: u32,
) ErrorContext {
return ErrorContext{
.error_type = err,
.message = message,
.file = file,
.line = line,
.timestamp = std.time.milliTimestamp(),
};
}
// TODO: エラーコンテキストを表示
fn print(self: ErrorContext) void {
std.debug.print(
"[ERROR] {} at {s}:{}\n Message: {s}\n Time: {}\n",
.{self.error_type, self.file, self.line, self.message, self.timestamp}
);
}
};
const ErrorLogger = struct {
contexts: std.ArrayList(ErrorContext),
allocator: std.mem.Allocator,
fn init(allocator: std.mem.Allocator) ErrorLogger {
return ErrorLogger{
.contexts = std.ArrayList(ErrorContext).init(allocator),
.allocator = allocator,
};
}
fn deinit(self: *ErrorLogger) void {
self.contexts.deinit();
}
// TODO: エラーをログに記録
fn logError(
self: *ErrorLogger,
err: AppError,
message: []const u8,
file: []const u8,
line: u32,
) !void {
const context = ErrorContext.create(err, message, file, line);
try self.contexts.append(context);
context.print();
}
// TODO: すべてのエラーを表示
fn printAll(self: *ErrorLogger) void {
std.debug.print("\n=== Error Log ({} entries) ===\n", .{self.contexts.items.len});
for (self.contexts.items, 0..) |ctx, i| {
std.debug.print("\nError #{}:\n", .{i + 1});
ctx.print();
}
}
};
pub fn main() !void {
const allocator = std.heap.page_allocator;
var logger = ErrorLogger.init(allocator);
defer logger.deinit();
std.debug.print("=== Error Context System ===\n\n", .{});
// エラーを記録
try logger.logError(AppError.ValidationFailed, "Invalid input", "main.zig", 123);
std.time.sleep(10 * std.time.ns_per_ms);
try logger.logError(AppError.ProcessingFailed, "Cannot process data", "processor.zig", 456);
std.time.sleep(10 * std.time.ns_per_ms);
try logger.logError(AppError.SystemError, "Out of memory", "allocator.zig", 789);
// すべてのエラーを表示
logger.printAll();
}
Bonus 2: カスタムパニックハンドラ (10点)
カスタムパニックハンドラを実装してください。
ファイル: bonus2/panic_handler.zig
const std = @import("std");
const builtin = @import("builtin");
// TODO: カスタムパニックハンドラ
pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
_ = error_return_trace;
_ = ret_addr;
std.debug.print("\n" ++ "=" ** 60 ++ "\n", .{});
std.debug.print("PANIC: Custom Handler Triggered!\n", .{});
std.debug.print("=" ** 60 ++ "\n", .{});
std.debug.print("Message: {s}\n", .{msg});
std.debug.print("Time: {}\n", .{std.time.milliTimestamp()});
if (builtin.mode == .Debug) {
std.debug.print("Debug mode: Stack trace available\n", .{});
}
std.debug.print("=" ** 60 ++ "\n\n", .{});
std.process.exit(1);
}
fn causesPanic() void {
const x: u8 = 255;
_ = x + 1; // オーバーフローでパニック(デバッグモード)
}
pub fn main() void {
std.debug.print("=== Custom Panic Handler ===\n\n", .{});
std.debug.print("This will trigger a custom panic...\n", .{});
// Note: この例はリリースビルドでは動作が異なります
// デバッグビルドで実行してください: zig build-exe -O Debug panic_handler.zig
causesPanic();
}
評価基準
| 項目 | 配点 |
|---|---|
| Part 1: ファイルシステムシミュレータ | 20点 |
| Part 2: リソース管理システム | 20点 |
| Part 3: エラーチェーンとラッピング | 20点 |
| Part 4: リトライとフォールバック | 20点 |
| **マンダトリー合計** | **80点** |
| Bonus 1: エラーコンテキストシステム | 10点 |
| Bonus 2: カスタムパニックハンドラ | 10点 |
| **ボーナス合計** | **20点** |
合格基準
- マンダトリー: 64点以上で合格(80点満点の80%)
- ボーナス: 追加評価(最終成績の加算)
提出方法
exercise09/
├── part1/
│ └── filesystem.zig
├── part2/
│ └── resource_manager.zig
├── part3/
│ └── error_chain.zig
├── part4/
│ └── retry.zig
├── bonus1/
│ └── error_context.zig
└── bonus2/
└── panic_handler.zig
テスト実行
# マンダトリー
zig run part1/filesystem.zig
zig run part2/resource_manager.zig
zig run part3/error_chain.zig
zig run part4/retry.zig
# ボーナス
zig run bonus1/error_context.zig
zig build-exe -O Debug bonus2/panic_handler.zig && ./panic_handler
ヒント
const MyError = error{
ErrorOne,
ErrorTwo,
};
- errdefer の使用:
const resource = try allocate();
errdefer deallocate(resource);
- エラーのラッピング:
lowLevelFunc() catch |err| {
return HighLevelError.SomethingWrong;
};
- リトライパターン:
var attempts: u32 = 0;
while (attempts < max) : (attempts += 1) {
if (operation()) |result| return result else |_| continue;
}
参考資料
- Zig Language Reference - Errors: https://ziglang.org/documentation/master/#Errors
- Ziglearn - Error Handling: https://ziglearn.org/chapter-1/#errors
- Zig Standard Library - Error Sets: https://ziglang.org/documentation/master/std/