課題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/