課題10: 構造体と列挙型の実践

マンダトリー要件 (80点)

Part 1: 幾何学ライブラリ (20点)

構造体を使った幾何学図形のライブラリを実装してください。

ファイル: part1/geometry.zig

const std = @import("std");

const Point = struct {
    x: f64,
    y: f64,

    // TODO: 2点間の距離を計算
    pub fn distance(self: Point, other: Point) f64 {
        // 実装
    }

    // TODO: 原点からの距離
    pub fn magnitude(self: Point) f64 {
        // 実装
    }
};

const Circle = struct {
    center: Point,
    radius: f64,

    // TODO: 面積を計算
    pub fn area(self: Circle) f64 {
        // 実装
    }

    // TODO: 円周を計算
    pub fn circumference(self: Circle) f64 {
        // 実装
    }

    // TODO: 点が円の内部にあるか判定
    pub fn contains(self: Circle, point: Point) bool {
        // 実装
    }

    // TODO: 2つの円が重なっているか判定
    pub fn overlaps(self: Circle, other: Circle) bool {
        // 実装
    }
};

const Rectangle = struct {
    top_left: Point,
    width: f64,
    height: f64,

    // TODO: 面積を計算
    pub fn area(self: Rectangle) f64 {
        // 実装
    }

    // TODO: 周囲の長さを計算
    pub fn perimeter(self: Rectangle) f64 {
        // 実装
    }

    // TODO: 点が矩形の内部にあるか判定
    pub fn contains(self: Rectangle, point: Point) bool {
        // 実装
    }

    // TODO: 中心点を取得
    pub fn center(self: Rectangle) Point {
        // 実装
    }
};

const Triangle = struct {
    p1: Point,
    p2: Point,
    p3: Point,

    // TODO: 面積を計算(ヘロンの公式)
    pub fn area(self: Triangle) f64 {
        // 実装
    }

    // TODO: 周囲の長さを計算
    pub fn perimeter(self: Triangle) f64 {
        // 実装
    }
};

pub fn main() void {
    std.debug.print("=== Geometry Library ===\n\n", .{});

    // Point
    const p1 = Point{ .x = 0.0, .y = 0.0 };
    const p2 = Point{ .x = 3.0, .y = 4.0 };
    std.debug.print("Distance: {d:.2}\n", .{p1.distance(p2)});

    // Circle
    const circle1 = Circle{ .center = Point{ .x = 0.0, .y = 0.0 }, .radius = 5.0 };
    const circle2 = Circle{ .center = Point{ .x = 8.0, .y = 0.0 }, .radius = 3.0 };
    std.debug.print("\nCircle area: {d:.2}\n", .{circle1.area()});
    std.debug.print("Circle circumference: {d:.2}\n", .{circle1.circumference()});
    std.debug.print("Circles overlap: {}\n", .{circle1.overlaps(circle2)});

    // Rectangle
    const rect = Rectangle{ .top_left = Point{ .x = 0.0, .y = 0.0 }, .width = 10.0, .height = 5.0 };
    std.debug.print("\nRectangle area: {d:.2}\n", .{rect.area()});
    std.debug.print("Rectangle perimeter: {d:.2}\n", .{rect.perimeter()});
    std.debug.print("Rectangle center: ({d:.2}, {d:.2})\n", .{rect.center().x, rect.center().y});

    // Triangle
    const tri = Triangle{
        .p1 = Point{ .x = 0.0, .y = 0.0 },
        .p2 = Point{ .x = 4.0, .y = 0.0 },
        .p3 = Point{ .x = 2.0, .y = 3.0 },
    };
    std.debug.print("\nTriangle area: {d:.2}\n", .{tri.area()});
    std.debug.print("Triangle perimeter: {d:.2}\n", .{tri.perimeter()});
}

Part 2: HTTPステータスモデル (20点)

列挙型とタグ付きユニオンを使ってHTTPレスポンスをモデリングしてください。

ファイル: part2/http.zig

const std = @import("std");

const HttpMethod = enum {
    GET,
    POST,
    PUT,
    DELETE,
    PATCH,
    HEAD,
    OPTIONS,

    // TODO: メソッドが安全か(副作用がないか)判定
    pub fn isSafe(self: HttpMethod) bool {
        // 実装
    }

    // TODO: メソッドがべき等か判定
    pub fn isIdempotent(self: HttpMethod) bool {
        // 実装
    }
};

const HttpStatus = enum(u16) {
    // 2xx Success
    OK = 200,
    Created = 201,
    Accepted = 202,
    NoContent = 204,

    // 3xx Redirection
    MovedPermanently = 301,
    Found = 302,
    NotModified = 304,

    // 4xx Client Error
    BadRequest = 400,
    Unauthorized = 401,
    Forbidden = 403,
    NotFound = 404,
    MethodNotAllowed = 405,

    // 5xx Server Error
    InternalServerError = 500,
    NotImplemented = 501,
    ServiceUnavailable = 503,

    // TODO: ステータスが成功か判定
    pub fn isSuccess(self: HttpStatus) bool {
        // 実装
    }

    // TODO: ステータスがリダイレクトか判定
    pub fn isRedirect(self: HttpStatus) bool {
        // 実装
    }

    // TODO: ステータスがクライアントエラーか判定
    pub fn isClientError(self: HttpStatus) bool {
        // 実装
    }

    // TODO: ステータスがサーバーエラーか判定
    pub fn isServerError(self: HttpStatus) bool {
        // 実装
    }

    // TODO: ステータスメッセージを取得
    pub fn message(self: HttpStatus) []const u8 {
        return switch (self) {
            .OK => "OK",
            .Created => "Created",
            .Accepted => "Accepted",
            .NoContent => "No Content",
            .MovedPermanently => "Moved Permanently",
            .Found => "Found",
            .NotModified => "Not Modified",
            .BadRequest => "Bad Request",
            .Unauthorized => "Unauthorized",
            .Forbidden => "Forbidden",
            .NotFound => "Not Found",
            .MethodNotAllowed => "Method Not Allowed",
            .InternalServerError => "Internal Server Error",
            .NotImplemented => "Not Implemented",
            .ServiceUnavailable => "Service Unavailable",
        };
    }
};

const HttpResponse = union(enum) {
    Success: struct {
        status: HttpStatus,
        body: []const u8,
    },
    Redirect: struct {
        status: HttpStatus,
        location: []const u8,
    },
    Error: struct {
        status: HttpStatus,
        message: []const u8,
    },

    // TODO: レスポンスを表示
    pub fn print(self: HttpResponse) void {
        switch (self) {
            .Success => |res| {
                std.debug.print("Success: {} {s}\nBody: {s}\n",
                    .{@intFromEnum(res.status), res.status.message(), res.body});
            },
            .Redirect => |res| {
                std.debug.print("Redirect: {} {s}\nLocation: {s}\n",
                    .{@intFromEnum(res.status), res.status.message(), res.location});
            },
            .Error => |res| {
                std.debug.print("Error: {} {s}\nMessage: {s}\n",
                    .{@intFromEnum(res.status), res.status.message(), res.message});
            },
        }
    }
};

pub fn main() void {
    std.debug.print("=== HTTP Model ===\n\n", .{});

    // HTTP Method
    std.debug.print("GET is safe: {}\n", .{HttpMethod.GET.isSafe()});
    std.debug.print("POST is idempotent: {}\n\n", .{HttpMethod.POST.isIdempotent()});

    // HTTP Status
    const status = HttpStatus.OK;
    std.debug.print("Status: {} {s}\n", .{@intFromEnum(status), status.message()});
    std.debug.print("Is success: {}\n\n", .{status.isSuccess()});

    // HTTP Response
    const responses = [_]HttpResponse{
        HttpResponse{ .Success = .{ .status = .OK, .body = "Hello, World!" } },
        HttpResponse{ .Redirect = .{ .status = .MovedPermanently, .location = "/new-url" } },
        HttpResponse{ .Error = .{ .status = .NotFound, .message = "Page not found" } },
    };

    for (responses, 0..) |res, i| {
        std.debug.print("Response #{}:\n", .{i + 1});
        res.print();
        std.debug.print("\n", .{});
    }
}

Part 3: パック構造体でビットフィールド (20点)

パック構造体を使ってビットレベルのデータ構造を実装してください。

ファイル: part3/bitfields.zig

const std = @import("std");

// ファイルパーミッション(Unix形式)
const FilePermissions = packed struct {
    // Others
    others_execute: u1,
    others_write: u1,
    others_read: u1,
    // Group
    group_execute: u1,
    group_write: u1,
    group_read: u1,
    // Owner
    owner_execute: u1,
    owner_write: u1,
    owner_read: u1,

    // TODO: 8進数表記に変換
    pub fn toOctal(self: FilePermissions) u16 {
        // 実装
    }

    // TODO: 8進数から作成
    pub fn fromOctal(octal: u16) FilePermissions {
        // 実装
    }

    // TODO: 文字列表記(例: "rwxr-xr--")
    pub fn toString(self: FilePermissions, buf: []u8) []const u8 {
        _ = self;
        _ = buf;
        // 実装
        return "";
    }
};

// IPv4アドレス
const IPv4Address = packed struct {
    octet4: u8,
    octet3: u8,
    octet2: u8,
    octet1: u8,

    // TODO: u32に変換
    pub fn toU32(self: IPv4Address) u32 {
        return @bitCast(self);
    }

    // TODO: u32から作成
    pub fn fromU32(value: u32) IPv4Address {
        return @bitCast(value);
    }

    // TODO: 文字列表記(例: "192.168.1.1")
    pub fn format(
        self: IPv4Address,
        comptime fmt: []const u8,
        options: std.fmt.FormatOptions,
        writer: anytype,
    ) !void {
        _ = fmt;
        _ = options;
        try writer.print("{}.{}.{}.{}", .{
            self.octet1, self.octet2, self.octet3, self.octet4
        });
    }
};

// RGBカラー(24ビット)
const RGB = packed struct {
    b: u8,
    g: u8,
    r: u8,

    // TODO: u32に変換(0xRRGGBB形式)
    pub fn toU32(self: RGB) u32 {
        return @as(u32, self.r) << 16 | @as(u32, self.g) << 8 | @as(u32, self.b);
    }

    // TODO: u32から作成
    pub fn fromU32(value: u32) RGB {
        return RGB{
            .r = @truncate((value >> 16) & 0xFF),
            .g = @truncate((value >> 8) & 0xFF),
            .b = @truncate(value & 0xFF),
        };
    }

    // TODO: グレースケールに変換
    pub fn toGrayscale(self: RGB) RGB {
        // 実装: 0.299*R + 0.587*G + 0.114*B
    }
};

pub fn main() void {
    std.debug.print("=== Bit Fields ===\n\n", .{});

    // File Permissions
    const perms = FilePermissions.fromOctal(0o755);
    std.debug.print("Permissions (octal): 0o{o}\n", .{perms.toOctal()});
    var buf: [9]u8 = undefined;
    std.debug.print("Permissions (string): {s}\n\n", .{perms.toString(&buf)});

    // IPv4 Address
    const ip = IPv4Address{ .octet1 = 192, .octet2 = 168, .octet3 = 1, .octet4 = 1 };
    std.debug.print("IP Address: {}\n", .{ip});
    std.debug.print("IP as u32: 0x{x}\n\n", .{ip.toU32()});

    // RGB Color
    const red = RGB{ .r = 255, .g = 0, .b = 0 };
    std.debug.print("Red: 0x{x:0>6}\n", .{red.toU32()});
    const gray = red.toGrayscale();
    std.debug.print("Grayscale: R={}, G={}, B={}\n", .{gray.r, gray.g, gray.b});
}

Part 4: ジェネリックコレクション (20点)

ジェネリック構造体を使ってコレクションを実装してください。

ファイル: part4/collections.zig

const std = @import("std");

// ジェネリックなペア型
fn Pair(comptime T: type, comptime U: type) type {
    return struct {
        first: T,
        second: U,

        const Self = @This();

        pub fn init(first: T, second: U) Self {
            return Self{ .first = first, .second = second };
        }

        pub fn swap(self: Self) Pair(U, T) {
            return Pair(U, T).init(self.second, self.first);
        }
    };
}

// ジェネリックなResult型
fn Result(comptime T: type, comptime E: type) type {
    return union(enum) {
        Ok: T,
        Err: E,

        const Self = @This();

        // TODO: 成功か判定
        pub fn isOk(self: Self) bool {
            // 実装
        }

        // TODO: エラーか判定
        pub fn isErr(self: Self) bool {
            // 実装
        }

        // TODO: 値をアンラップ(エラーの場合はパニック)
        pub fn unwrap(self: Self) T {
            // 実装
        }

        // TODO: デフォルト値を返す
        pub fn unwrapOr(self: Self, default: T) T {
            // 実装
        }

        // TODO: map関数
        pub fn map(self: Self, comptime R: type, func: fn(T) R) Result(R, E) {
            // 実装
        }
    };
}

// ジェネリックなOption型
fn Option(comptime T: type) type {
    return union(enum) {
        Some: T,
        None,

        const Self = @This();

        // TODO: 値があるか判定
        pub fn isSome(self: Self) bool {
            // 実装
        }

        // TODO: 値がないか判定
        pub fn isNone(self: Self) bool {
            // 実装
        }

        // TODO: 値をアンラップ
        pub fn unwrap(self: Self) T {
            // 実装
        }

        // TODO: デフォルト値を返す
        pub fn unwrapOr(self: Self, default: T) T {
            // 実装
        }

        // TODO: filter関数
        pub fn filter(self: Self, predicate: fn(T) bool) Self {
            // 実装
        }
    };
}

pub fn main() void {
    std.debug.print("=== Generic Collections ===\n\n", .{});

    // Pair
    const pair1 = Pair(i32, []const u8).init(42, "answer");
    std.debug.print("Pair: ({}, {s})\n", .{pair1.first, pair1.second});
    const pair2 = pair1.swap();
    std.debug.print("Swapped: ({s}, {})\n\n", .{pair2.first, pair2.second});

    // Result
    const ok = Result(i32, []const u8){ .Ok = 100 };
    const err = Result(i32, []const u8){ .Err = "error" };
    std.debug.print("Result is ok: {}\n", .{ok.isOk()});
    std.debug.print("Result value: {}\n", .{ok.unwrapOr(0)});
    std.debug.print("Error value: {}\n\n", .{err.unwrapOr(0)});

    // Option
    const some = Option(i32){ .Some = 42 };
    const none = Option(i32).None;
    std.debug.print("Option has value: {}\n", .{some.isSome()});
    std.debug.print("Some value: {}\n", .{some.unwrapOr(0)});
    std.debug.print("None value: {}\n", .{none.unwrapOr(0)});
}

ボーナス課題 (20点)

Bonus 1: Expression AST (10点)

タグ付きユニオンを使って数式の抽象構文木を実装してください。

ファイル: bonus1/ast.zig

const std = @import("std");

const Expr = union(enum) {
    Number: i32,
    Add: struct { left: *Expr, right: *Expr },
    Sub: struct { left: *Expr, right: *Expr },
    Mul: struct { left: *Expr, right: *Expr },
    Div: struct { left: *Expr, right: *Expr },

    // TODO: 式を評価
    pub fn eval(self: *const Expr) i32 {
        return switch (self.*) {
            .Number => |n| n,
            .Add => |bin| bin.left.eval() + bin.right.eval(),
            .Sub => |bin| bin.left.eval() - bin.right.eval(),
            .Mul => |bin| bin.left.eval() * bin.right.eval(),
            .Div => |bin| @divTrunc(bin.left.eval(), bin.right.eval()),
        };
    }

    // TODO: 式を文字列化
    pub fn toString(self: *const Expr, allocator: std.mem.Allocator) ![]u8 {
        _ = self;
        _ = allocator;
        // 実装
        return "";
    }

    // TODO: 式の深さを取得
    pub fn depth(self: *const Expr) usize {
        // 実装
    }
};

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    // (2 + 3) * 4
    var num2 = Expr{ .Number = 2 };
    var num3 = Expr{ .Number = 3 };
    var add = Expr{ .Add = .{ .left = &num2, .right = &num3 } };
    var num4 = Expr{ .Number = 4 };
    var mul = Expr{ .Mul = .{ .left = &add, .right = &num4 } };

    std.debug.print("Expression: {s}\n", .{try mul.toString(allocator)});
    std.debug.print("Result: {}\n", .{mul.eval()});
    std.debug.print("Depth: {}\n", .{mul.depth()});
}

Bonus 2: State Machine (10点)

列挙型とタグ付きユニオンを使って状態機械を実装してください。

ファイル: bonus2/state_machine.zig

const std = @import("std");

const State = enum {
    Idle,
    Running,
    Paused,
    Stopped,
    Error,
};

const Event = union(enum) {
    Start,
    Pause,
    Resume,
    Stop,
    Error: []const u8,
};

const StateMachine = struct {
    state: State,

    pub fn init() StateMachine {
        return StateMachine{ .state = .Idle };
    }

    // TODO: イベントを処理して状態遷移
    pub fn handleEvent(self: *StateMachine, event: Event) void {
        std.debug.print("Current state: {}, Event: {}\n", .{self.state, event});

        const new_state = switch (self.state) {
            .Idle => switch (event) {
                .Start => State.Running,
                .Error => State.Error,
                else => self.state,
            },
            .Running => switch (event) {
                .Pause => State.Paused,
                .Stop => State.Stopped,
                .Error => State.Error,
                else => self.state,
            },
            .Paused => switch (event) {
                .Resume => State.Running,
                .Stop => State.Stopped,
                .Error => State.Error,
                else => self.state,
            },
            .Stopped => switch (event) {
                .Start => State.Running,
                else => self.state,
            },
            .Error => switch (event) {
                .Start => State.Idle,
                else => self.state,
            },
        };

        if (new_state != self.state) {
            std.debug.print("State transition: {} -> {}\n", .{self.state, new_state});
            self.state = new_state;
        } else {
            std.debug.print("Invalid transition, staying in {}\n", .{self.state});
        }
        std.debug.print("\n", .{});
    }

    // TODO: 有効な遷移か確認
    pub fn canTransition(self: StateMachine, event: Event) bool {
        // 実装
    }
};

pub fn main() void {
    std.debug.print("=== State Machine ===\n\n", .{});

    var machine = StateMachine.init();

    machine.handleEvent(Event.Start);
    machine.handleEvent(Event.Pause);
    machine.handleEvent(Event.Resume);
    machine.handleEvent(Event.Stop);
    machine.handleEvent(Event.Start);
    machine.handleEvent(Event{ .Error = "Something went wrong" });
    machine.handleEvent(Event.Start);
}

評価基準

項目 配点
Part 1: 幾何学ライブラリ 20点
Part 2: HTTPステータスモデル 20点
Part 3: パック構造体でビットフィールド 20点
Part 4: ジェネリックコレクション 20点
**マンダトリー合計** **80点**
Bonus 1: Expression AST 10点
Bonus 2: State Machine 10点
**ボーナス合計** **20点**

合格基準

  • マンダトリー: 64点以上で合格(80点満点の80%)
  • ボーナス: 追加評価(最終成績の加算)
  • 提出方法

    exercise10/
    ├── part1/
    │   └── geometry.zig
    ├── part2/
    │   └── http.zig
    ├── part3/
    │   └── bitfields.zig
    ├── part4/
    │   └── collections.zig
    ├── bonus1/
    │   └── ast.zig
    └── bonus2/
        └── state_machine.zig
    

    テスト実行

    # マンダトリー
    zig run part1/geometry.zig
    zig run part2/http.zig
    zig run part3/bitfields.zig
    zig run part4/collections.zig
    
    # ボーナス
    zig run bonus1/ast.zig
    zig run bonus2/state_machine.zig
    

    参考資料

  • Zig Language Reference - Structs: https://ziglang.org/documentation/master/#struct
  • Zig Language Reference - Enums: https://ziglang.org/documentation/master/#enum
  • Zig Language Reference - Unions: https://ziglang.org/documentation/master/#union