第10章: 構造体と列挙型
学習目標
この章を終えると、以下ができるようになります:
- 構造体を定義し、メソッドを実装できる
- パック構造体でメモリ効率を向上できる
- 列挙型とタグ付きユニオンを使ってデータをモデリングできる
- ジェネリック構造体を作成できる
構造体の基本
構造体の定義
構造体は、関連するデータをまとめるための基本的なデータ構造です。
const std = @import("std");
const Point = struct {
x: f32,
y: f32,
};
const Rectangle = struct {
top_left: Point,
width: f32,
height: f32,
};
pub fn example() void {
const point = Point{ .x = 10.0, .y = 20.0 };
std.debug.print("Point: ({d:.1}, {d:.1})\n", .{point.x, point.y});
const rect = Rectangle{
.top_left = Point{ .x = 0.0, .y = 0.0 },
.width = 100.0,
.height = 50.0,
};
std.debug.print("Rectangle: ({d:.1}, {d:.1}), {d:.1}x{d:.1}\n",
.{rect.top_left.x, rect.top_left.y, rect.width, rect.height});
}
メソッドの定義
構造体にメソッド(関数)を追加できます。
const std = @import("std");
const Circle = struct {
center: Point,
radius: f32,
// メソッド: 面積を計算
pub fn area(self: Circle) f32 {
return std.math.pi * self.radius * self.radius;
}
// メソッド: 円周を計算
pub fn circumference(self: Circle) f32 {
return 2.0 * std.math.pi * self.radius;
}
// メソッド: 点が円の内部にあるか判定
pub fn contains(self: Circle, point: Point) bool {
const dx = point.x - self.center.x;
const dy = point.y - self.center.y;
const distance = std.math.sqrt(dx * dx + dy * dy);
return distance <= self.radius;
}
};
const Point = struct {
x: f32,
y: f32,
};
pub fn example() void {
const circle = Circle{
.center = Point{ .x = 0.0, .y = 0.0 },
.radius = 10.0,
};
std.debug.print("Area: {d:.2}\n", .{circle.area()});
std.debug.print("Circumference: {d:.2}\n", .{circle.circumference()});
const p1 = Point{ .x = 5.0, .y = 5.0 };
const p2 = Point{ .x = 15.0, .y = 15.0 };
std.debug.print("Point (5, 5) inside: {}\n", .{circle.contains(p1)});
std.debug.print("Point (15, 15) inside: {}\n", .{circle.contains(p2)});
}
デフォルト値
構造体のフィールドにデフォルト値を設定できます。
const std = @import("std");
const Config = struct {
host: []const u8 = "localhost",
port: u16 = 8080,
timeout_ms: u32 = 5000,
debug: bool = false,
};
pub fn example() void {
// すべてデフォルト値
const config1 = Config{};
std.debug.print("Config1: {s}:{}, timeout={}, debug={}\n",
.{config1.host, config1.port, config1.timeout_ms, config1.debug});
// 一部を上書き
const config2 = Config{
.port = 3000,
.debug = true,
};
std.debug.print("Config2: {s}:{}, timeout={}, debug={}\n",
.{config2.host, config2.port, config2.timeout_ms, config2.debug});
}
コンストラクタパターン
構造体の初期化を簡潔にするコンストラクタ関数を定義できます。
const std = @import("std");
const User = struct {
id: u32,
name: []const u8,
email: []const u8,
active: bool,
// コンストラクタ
pub fn init(id: u32, name: []const u8, email: []const u8) User {
return User{
.id = id,
.name = name,
.email = email,
.active = true,
};
}
// ビルダーパターン
pub fn setActive(self: User, active: bool) User {
var new_user = self;
new_user.active = active;
return new_user;
}
pub fn print(self: User) void {
std.debug.print("User #{}: {s} <{s}> [{}]\n",
.{self.id, self.name, self.email, if (self.active) "active" else "inactive"});
}
};
pub fn example() void {
const user1 = User.init(1, "Alice", "alice@example.com");
user1.print();
const user2 = user1.setActive(false);
user2.print();
}
パック構造体
基本的なパック構造体
パック構造体は、メモリレイアウトが保証された構造体です。
const std = @import("std");
const Flags = packed struct {
read: bool,
write: bool,
execute: bool,
reserved: u5,
};
pub fn example() void {
const flags = Flags{
.read = true,
.write = true,
.execute = false,
.reserved = 0,
};
std.debug.print("Size: {} bytes\n", .{@sizeOf(Flags)});
std.debug.print("Read: {}, Write: {}, Execute: {}\n",
.{flags.read, flags.write, flags.execute});
// ビットパターンとして扱える
const bits: u8 = @bitCast(flags);
std.debug.print("Bit pattern: 0b{b:0>8}\n", .{bits});
}
ビットフィールドの活用
const std = @import("std");
const Color = packed struct {
r: u8,
g: u8,
b: u8,
a: u8,
pub fn init(r: u8, g: u8, b: u8, a: u8) Color {
return Color{ .r = r, .g = g, .b = b, .a = a };
}
pub fn toU32(self: Color) u32 {
return @bitCast(self);
}
pub fn fromU32(value: u32) Color {
return @bitCast(value);
}
};
pub fn example() void {
const red = Color.init(255, 0, 0, 255);
std.debug.print("Red: R={}, G={}, B={}, A={}\n",
.{red.r, red.g, red.b, red.a});
const value = red.toU32();
std.debug.print("As u32: 0x{x:0>8}\n", .{value});
const restored = Color.fromU32(value);
std.debug.print("Restored: R={}, G={}, B={}, A={}\n",
.{restored.r, restored.g, restored.b, restored.a});
}
ハードウェアレジスタのモデリング
const std = @import("std");
const StatusRegister = packed struct {
carry: u1,
zero: u1,
interrupt_disable: u1,
decimal_mode: u1,
break_command: u1,
unused: u1,
overflow: u1,
negative: u1,
pub fn init() StatusRegister {
return @bitCast(@as(u8, 0x00));
}
pub fn setFlag(self: *StatusRegister, flag: Flag, value: bool) void {
switch (flag) {
.Carry => self.carry = @intFromBool(value),
.Zero => self.zero = @intFromBool(value),
.Overflow => self.overflow = @intFromBool(value),
.Negative => self.negative = @intFromBool(value),
}
}
pub fn getFlag(self: StatusRegister, flag: Flag) bool {
return switch (flag) {
.Carry => self.carry == 1,
.Zero => self.zero == 1,
.Overflow => self.overflow == 1,
.Negative => self.negative == 1,
};
}
};
const Flag = enum {
Carry,
Zero,
Overflow,
Negative,
};
pub fn example() void {
var status = StatusRegister.init();
status.setFlag(.Zero, true);
status.setFlag(.Carry, true);
std.debug.print("Zero flag: {}\n", .{status.getFlag(.Zero)});
std.debug.print("Carry flag: {}\n", .{status.getFlag(.Carry)});
std.debug.print("Negative flag: {}\n", .{status.getFlag(.Negative)});
}
列挙型
基本的な列挙型
列挙型は、関連する定数の集合を定義します。
const std = @import("std");
const Direction = enum {
North,
South,
East,
West,
pub fn opposite(self: Direction) Direction {
return switch (self) {
.North => .South,
.South => .North,
.East => .West,
.West => .East,
};
}
pub fn rotate90(self: Direction) Direction {
return switch (self) {
.North => .East,
.East => .South,
.South => .West,
.West => .North,
};
}
};
pub fn example() void {
const dir = Direction.North;
std.debug.print("Direction: {}\n", .{dir});
std.debug.print("Opposite: {}\n", .{dir.opposite()});
std.debug.print("Rotate 90°: {}\n", .{dir.rotate90()});
}
列挙型に値を付与
const std = @import("std");
const HttpStatus = enum(u16) {
Ok = 200,
Created = 201,
NoContent = 204,
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404,
InternalServerError = 500,
pub fn isSuccess(self: HttpStatus) bool {
return @intFromEnum(self) >= 200 and @intFromEnum(self) < 300;
}
pub fn isError(self: HttpStatus) bool {
return @intFromEnum(self) >= 400;
}
};
pub fn example() void {
const status1 = HttpStatus.Ok;
const status2 = HttpStatus.NotFound;
std.debug.print("Status {}: success={}, error={}\n",
.{@intFromEnum(status1), status1.isSuccess(), status1.isError()});
std.debug.print("Status {}: success={}, error={}\n",
.{@intFromEnum(status2), status2.isSuccess(), status2.isError()});
}
列挙型のイテレーション
const std = @import("std");
const Color = enum {
Red,
Green,
Blue,
Yellow,
Magenta,
Cyan,
pub fn toRGB(self: Color) [3]u8 {
return switch (self) {
.Red => [3]u8{ 255, 0, 0 },
.Green => [3]u8{ 0, 255, 0 },
.Blue => [3]u8{ 0, 0, 255 },
.Yellow => [3]u8{ 255, 255, 0 },
.Magenta => [3]u8{ 255, 0, 255 },
.Cyan => [3]u8{ 0, 255, 255 },
};
}
};
pub fn example() void {
// すべての列挙値を取得
const colors = std.meta.fields(Color);
inline for (colors) |color_info| {
const color = @field(Color, color_info.name);
const rgb = color.toRGB();
std.debug.print("{s}: RGB({}, {}, {})\n",
.{color_info.name, rgb[0], rgb[1], rgb[2]});
}
}
タグ付きユニオン
基本的なタグ付きユニオン
タグ付きユニオンは、複数の型のうち1つを保持できる型です。
const std = @import("std");
const Value = union(enum) {
integer: i32,
float: f64,
boolean: bool,
string: []const u8,
pub fn print(self: Value) void {
switch (self) {
.integer => |val| std.debug.print("Integer: {}\n", .{val}),
.float => |val| std.debug.print("Float: {d:.2}\n", .{val}),
.boolean => |val| std.debug.print("Boolean: {}\n", .{val}),
.string => |val| std.debug.print("String: {s}\n", .{val}),
}
}
};
pub fn example() void {
const values = [_]Value{
Value{ .integer = 42 },
Value{ .float = 3.14 },
Value{ .boolean = true },
Value{ .string = "Hello" },
};
for (values) |val| {
val.print();
}
}
ペイロード付きタグ付きユニオン
const std = @import("std");
const Message = union(enum) {
Quit,
Move: struct { x: i32, y: i32 },
Write: []const u8,
ChangeColor: struct { r: u8, g: u8, b: u8 },
pub fn handle(self: Message) void {
switch (self) {
.Quit => std.debug.print("Quitting...\n", .{}),
.Move => |pos| std.debug.print("Moving to ({}, {})\n", .{pos.x, pos.y}),
.Write => |text| std.debug.print("Writing: {s}\n", .{text}),
.ChangeColor => |color| std.debug.print(
"Changing color to RGB({}, {}, {})\n",
.{color.r, color.g, color.b}
),
}
}
};
pub fn example() void {
const messages = [_]Message{
Message.Quit,
Message{ .Move = .{ .x = 10, .y = 20 } },
Message{ .Write = "Hello, World!" },
Message{ .ChangeColor = .{ .r = 255, .g = 0, .b = 0 } },
};
for (messages) |msg| {
msg.handle();
}
}
Result型のモデリング
const std = @import("std");
fn Result(comptime T: type, comptime E: type) type {
return union(enum) {
Ok: T,
Err: E,
pub fn isOk(self: @This()) bool {
return self == .Ok;
}
pub fn isErr(self: @This()) bool {
return self == .Err;
}
pub fn unwrap(self: @This()) T {
return switch (self) {
.Ok => |val| val,
.Err => |err| {
std.debug.print("Called unwrap on Err: {}\n", .{err});
@panic("unwrap on Err");
},
};
}
pub fn unwrapOr(self: @This(), default: T) T {
return switch (self) {
.Ok => |val| val,
.Err => default,
};
}
};
}
const ParseError = error{
InvalidFormat,
OutOfRange,
};
fn parseInt(str: []const u8) Result(i32, ParseError) {
_ = str;
// 簡略化のため固定値を返す
return Result(i32, ParseError){ .Ok = 42 };
}
pub fn example() void {
const result1 = parseInt("123");
if (result1.isOk()) {
std.debug.print("Parsed: {}\n", .{result1.unwrap()});
}
const result2 = Result(i32, ParseError){ .Err = ParseError.InvalidFormat };
const value = result2.unwrapOr(0);
std.debug.print("Default value: {}\n", .{value});
}
ジェネリック構造体
基本的なジェネリック構造体
const std = @import("std");
fn Stack(comptime T: type) type {
return struct {
items: []T,
len: usize,
allocator: std.mem.Allocator,
const Self = @This();
pub fn init(allocator: std.mem.Allocator, capacity: usize) !Self {
const items = try allocator.alloc(T, capacity);
return Self{
.items = items,
.len = 0,
.allocator = allocator,
};
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.items);
}
pub fn push(self: *Self, item: T) !void {
if (self.len >= self.items.len) {
return error.StackOverflow;
}
self.items[self.len] = item;
self.len += 1;
}
pub fn pop(self: *Self) ?T {
if (self.len == 0) {
return null;
}
self.len -= 1;
return self.items[self.len];
}
pub fn peek(self: *Self) ?T {
if (self.len == 0) {
return null;
}
return self.items[self.len - 1];
}
};
}
pub fn example() !void {
const allocator = std.heap.page_allocator;
var int_stack = try Stack(i32).init(allocator, 10);
defer int_stack.deinit();
try int_stack.push(1);
try int_stack.push(2);
try int_stack.push(3);
std.debug.print("Pop: {?}\n", .{int_stack.pop()});
std.debug.print("Peek: {?}\n", .{int_stack.peek()});
std.debug.print("Pop: {?}\n", .{int_stack.pop()});
}
ジェネリックなOption型
const std = @import("std");
fn Option(comptime T: type) type {
return union(enum) {
Some: T,
None,
const Self = @This();
pub fn isSome(self: Self) bool {
return self == .Some;
}
pub fn isNone(self: Self) bool {
return self == .None;
}
pub fn unwrap(self: Self) T {
return switch (self) {
.Some => |val| val,
.None => @panic("Called unwrap on None"),
};
}
pub fn unwrapOr(self: Self, default: T) T {
return switch (self) {
.Some => |val| val,
.None => default,
};
}
pub fn map(self: Self, comptime R: type, func: fn(T) R) Option(R) {
return switch (self) {
.Some => |val| Option(R){ .Some = func(val) },
.None => Option(R).None,
};
}
};
}
pub fn example() void {
const opt1 = Option(i32){ .Some = 42 };
const opt2 = Option(i32).None;
std.debug.print("opt1 is some: {}\n", .{opt1.isSome()});
std.debug.print("opt2 is none: {}\n", .{opt2.isNone()});
const value1 = opt1.unwrapOr(0);
const value2 = opt2.unwrapOr(0);
std.debug.print("opt1 value: {}\n", .{value1});
std.debug.print("opt2 value: {}\n", .{value2});
// map関数の使用
const double = struct {
fn func(x: i32) i32 {
return x * 2;
}
}.func;
const opt3 = opt1.map(i32, double);
std.debug.print("Mapped value: {}\n", .{opt3.unwrap()});
}
まとめ
この章では、構造体と列挙型について学びました:
次の章では、コンパイル時計算(comptime)について学びます。