目的
この記事では、Zigの哲学を体現し、低レベル操作を安全かつ効率的に行えるようになることを目指します。
前提知識:
- Intermediate編の内容(エラーハンドリング、アロケータ、comptime、タグ付きユニオン)
この記事で学ぶこと:
- カスタムアロケータの実装
- comptime による高度なメタプログラミング
- @TypeInfo によるリフレクション
- build.zig によるプロジェクト管理
- C FFI による既存ライブラリの活用
- 最適化とパフォーマンスチューニング
- メタプログラミングによるDSL構築
1. カスタムアロケータの実装
Allocator インターフェースの詳細
const std = @import("std");
// Allocator は構造体
pub const Allocator = struct {
ptr: *anyopaque,
vtable: *const VTable,
pub const VTable = struct {
alloc: *const fn (
ctx: *anyopaque,
len: usize,
ptr_align: u8,
ret_addr: usize,
) ?[*]u8,
resize: *const fn (
ctx: *anyopaque,
buf: []u8,
buf_align: u8,
new_len: usize,
ret_addr: usize,
) bool,
free: *const fn (
ctx: *anyopaque,
buf: []u8,
buf_align: u8,
ret_addr: usize,
) void,
};
// 便利なラッパー
pub fn alloc(self: Allocator, comptime T: type, n: usize) ![]T {
// 実装...
}
pub fn free(self: Allocator, memory: anytype) void {
// 実装...
}
};
ArenaAllocator の実装
const std = @import("std");
const SimpleArena = struct {
child_allocator: std.mem.Allocator,
buffer: std.ArrayList(u8),
offset: usize,
pub fn init(child_allocator: std.mem.Allocator) SimpleArena {
return SimpleArena{
.child_allocator = child_allocator,
.buffer = std.ArrayList(u8).init(child_allocator),
.offset = 0,
};
}
pub fn deinit(self: *SimpleArena) void {
self.buffer.deinit();
}
pub fn allocator(self: *SimpleArena) std.mem.Allocator {
return std.mem.Allocator{
.ptr = self,
.vtable = &.{
.alloc = alloc,
.resize = resize,
.free = free,
},
};
}
fn alloc(
ctx: *anyopaque,
len: usize,
ptr_align: u8,
ret_addr: usize,
) ?[*]u8 {
_ = ret_addr;
const self: *SimpleArena = @ptrCast(@alignCast(ctx));
// アライメントを考慮
const alignment = @as(usize, 1) << @intCast(ptr_align);
const offset_aligned = std.mem.alignForward(usize, self.offset, alignment);
const new_offset = offset_aligned + len;
// バッファが足りない場合は拡張
if (new_offset > self.buffer.items.len) {
const new_capacity = @max(new_offset, self.buffer.items.len * 2);
self.buffer.resize(new_capacity) catch return null;
}
self.offset = new_offset;
return self.buffer.items[offset_aligned..new_offset].ptr;
}
fn resize(
ctx: *anyopaque,
buf: []u8,
buf_align: u8,
new_len: usize,
ret_addr: usize,
) bool {
_ = ctx;
_ = buf;
_ = buf_align;
_ = new_len;
_ = ret_addr;
// 簡易実装: リサイズは未対応
return false;
}
fn free(
ctx: *anyopaque,
buf: []u8,
buf_align: u8,
ret_addr: usize,
) void {
_ = ctx;
_ = buf;
_ = buf_align;
_ = ret_addr;
// Arena は個別の free をサポートしない
}
pub fn reset(self: *SimpleArena) void {
self.offset = 0;
}
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var arena = SimpleArena.init(gpa.allocator());
defer arena.deinit();
const allocator = arena.allocator();
// 複数の割り当て
const slice1 = try allocator.alloc(u8, 100);
const slice2 = try allocator.alloc(i32, 50);
const slice3 = try allocator.alloc(f64, 25);
std.debug.print("Allocated {} bytes\n", .{arena.offset});
// 一括解放(reset)
arena.reset();
std.debug.print("After reset: {} bytes\n", .{arena.offset});
}
FixedBufferAllocator の実装
const std = @import("std");
const FixedBufferAllocator = struct {
buffer: []u8,
offset: usize,
pub fn init(buffer: []u8) FixedBufferAllocator {
return FixedBufferAllocator{
.buffer = buffer,
.offset = 0,
};
}
pub fn allocator(self: *FixedBufferAllocator) std.mem.Allocator {
return std.mem.Allocator{
.ptr = self,
.vtable = &.{
.alloc = alloc,
.resize = resize,
.free = free,
},
};
}
fn alloc(
ctx: *anyopaque,
len: usize,
ptr_align: u8,
ret_addr: usize,
) ?[*]u8 {
_ = ret_addr;
const self: *FixedBufferAllocator = @ptrCast(@alignCast(ctx));
const alignment = @as(usize, 1) << @intCast(ptr_align);
const offset_aligned = std.mem.alignForward(usize, self.offset, alignment);
const new_offset = offset_aligned + len;
if (new_offset > self.buffer.len) {
return null; // バッファが足りない
}
self.offset = new_offset;
return self.buffer[offset_aligned..new_offset].ptr;
}
fn resize(
ctx: *anyopaque,
buf: []u8,
buf_align: u8,
new_len: usize,
ret_addr: usize,
) bool {
_ = ctx;
_ = buf;
_ = buf_align;
_ = new_len;
_ = ret_addr;
return false;
}
fn free(
ctx: *anyopaque,
buf: []u8,
buf_align: u8,
ret_addr: usize,
) void {
_ = ctx;
_ = buf;
_ = buf_align;
_ = ret_addr;
}
pub fn reset(self: *FixedBufferAllocator) void {
self.offset = 0;
}
};
pub fn main() !void {
// スタック上のバッファ
var buffer: [4096]u8 = undefined;
var fba = FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
const data1 = try allocator.alloc(u8, 100);
const data2 = try allocator.alloc(i32, 50);
std.debug.print("Used: {}/{} bytes\n", .{fba.offset, buffer.len});
// リセット
fba.reset();
}
メモリプールの実装
const std = @import("std");
fn MemoryPool(comptime T: type, comptime pool_size: usize) type {
return struct {
const Self = @This();
buffer: [pool_size]T,
free_list: [pool_size]bool,
pub fn init() Self {
return Self{
.buffer = undefined,
.free_list = [_]bool{true} ** pool_size,
};
}
pub fn allocate(self: *Self) ?*T {
for (&self.free_list, 0..) |*is_free, i| {
if (is_free.*) {
is_free.* = false;
return &self.buffer[i];
}
}
return null;
}
pub fn deallocate(self: *Self, ptr: *T) void {
const index = (@intFromPtr(ptr) - @intFromPtr(&self.buffer)) / @sizeOf(T);
self.free_list[index] = true;
}
pub fn available(self: *Self) usize {
var count: usize = 0;
for (self.free_list) |is_free| {
if (is_free) count += 1;
}
return count;
}
};
}
const Entity = struct {
id: u32,
x: f32,
y: f32,
};
pub fn main() !void {
var pool = MemoryPool(Entity, 100).init();
// エンティティを確保
const e1 = pool.allocate() orelse return error.OutOfMemory;
e1.* = Entity{ .id = 1, .x = 10.0, .y = 20.0 };
const e2 = pool.allocate() orelse return error.OutOfMemory;
e2.* = Entity{ .id = 2, .x = 30.0, .y = 40.0 };
std.debug.print("Available: {}\n", .{pool.available()}); // 98
// 解放
pool.deallocate(e1);
std.debug.print("Available: {}\n", .{pool.available()}); // 99
}
学習ポイント:
- Allocator は vtable によるインターフェース
- ArenaAllocator は一括解放に最適
- FixedBufferAllocator はスタック上で動作
- メモリプールは固定サイズオブジェクトに最適
2. comptime の高度な使い方
型レベルプログラミング
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 switch (self) {
.ok => true,
.err => false,
};
}
pub fn unwrap(self: @This()) T {
return switch (self) {
.ok => |val| val,
.err => |e| @panic(@typeName(E)),
};
}
};
}
pub fn main() void {
const IntResult = Result(i32, []const u8);
const success = IntResult{ .ok = 42 };
const failure = IntResult{ .err = "something went wrong" };
std.debug.print("success.isOk() = {}\n", .{success.isOk()});
std.debug.print("failure.isOk() = {}\n", .{failure.isOk()});
std.debug.print("value = {}\n", .{success.unwrap()});
}
コンパイル時コード生成
const std = @import("std");
// フィールドを持つ構造体を生成
fn createStruct(comptime field_count: usize) type {
var fields: [field_count]std.builtin.Type.StructField = undefined;
inline for (0..field_count) |i| {
const name = std.fmt.comptimePrint("field{}", .{i});
fields[i] = .{
.name = name,
.type = i32,
.default_value = null,
.is_comptime = false,
.alignment = @alignOf(i32),
};
}
return @Type(.{
.Struct = .{
.layout = .auto,
.fields = &fields,
.decls = &.{},
.is_tuple = false,
},
});
}
pub fn main() void {
const MyStruct = createStruct(3);
var s: MyStruct = undefined;
s.field0 = 10;
s.field1 = 20;
s.field2 = 30;
std.debug.print("field0 = {}\n", .{s.field0});
std.debug.print("field1 = {}\n", .{s.field1});
std.debug.print("field2 = {}\n", .{s.field2});
}
条件付きコンパイル
const std = @import("std");
const builtin = @import("builtin");
pub fn main() void {
// OSに応じた処理
const os_name = comptime switch (builtin.os.tag) {
.linux => "Linux",
.windows => "Windows",
.macos => "macOS",
else => "Other",
};
std.debug.print("Running on: {s}\n", .{os_name});
// アーキテクチャに応じた処理
const arch = comptime switch (builtin.cpu.arch) {
.x86_64 => "x86_64",
.aarch64 => "ARM64",
else => "Other",
};
std.debug.print("Architecture: {s}\n", .{arch});
// ビルドモードに応じた処理
const mode = comptime switch (builtin.mode) {
.Debug => "Debug",
.ReleaseSafe => "ReleaseSafe",
.ReleaseFast => "ReleaseFast",
.ReleaseSmall => "ReleaseSmall",
};
std.debug.print("Build mode: {s}\n", .{mode});
}
comptime パラメータの活用
const std = @import("std");
// 型安全なビルダーパターン
fn Builder(comptime T: type) type {
return struct {
const Self = @This();
value: T,
pub fn init() Self {
return Self{ .value = undefined };
}
// すべてのフィールドにセッターを生成
pub usingnamespace blk: {
var decls: []const std.builtin.Type.Declaration = &.{};
const fields = @typeInfo(T).Struct.fields;
inline for (fields) |field| {
const setter_name = "set" ++ field.name;
// セッター関数を動的に生成
}
break :blk struct {};
};
pub fn build(self: Self) T {
return self.value;
}
};
}
const User = struct {
name: []const u8,
age: u32,
email: []const u8,
};
pub fn main() void {
var builder = Builder(User).init();
builder.value.name = "Alice";
builder.value.age = 30;
builder.value.email = "alice@example.com";
const user = builder.build();
std.debug.print("User: {s}, {}\n", .{user.name, user.age});
}
実例: 型安全なHTTPメソッド
const std = @import("std");
const HttpMethod = enum {
GET,
POST,
PUT,
DELETE,
PATCH,
pub fn asString(self: HttpMethod) []const u8 {
return switch (self) {
.GET => "GET",
.POST => "POST",
.PUT => "PUT",
.DELETE => "DELETE",
.PATCH => "PATCH",
};
}
};
fn Request(comptime method: HttpMethod) type {
return struct {
const Self = @This();
const method_str = method.asString();
path: []const u8,
body: if (method == .GET or method == .DELETE) void else []const u8,
pub fn init(path: []const u8) Self {
return Self{
.path = path,
.body = if (method == .GET or method == .DELETE) {} else "",
};
}
pub fn withBody(self: Self, body: []const u8) Self {
if (method == .GET or method == .DELETE) {
@compileError("GET and DELETE requests cannot have a body");
}
var new_self = self;
new_self.body = body;
return new_self;
}
pub fn send(self: Self) void {
std.debug.print("{s} {s}\n", .{method_str, self.path});
if (method != .GET and method != .DELETE) {
std.debug.print("Body: {s}\n", .{self.body});
}
}
};
}
pub fn main() void {
const get_req = Request(.GET).init("/users");
get_req.send();
const post_req = Request(.POST)
.init("/users")
.withBody("{\"name\": \"Alice\"}");
post_req.send();
// コンパイルエラー: GETリクエストにbodyは付けられない
// const bad_req = Request(.GET).init("/users").withBody("data");
}
学習ポイント:
- 型を返す関数で柔軟な設計
-
@Typeで型を動的生成 -
builtinで環境情報を取得 - コンパイル時エラーで型安全性を保証
3. @TypeInfo とリフレクション
@TypeInfo の基本
const std = @import("std");
pub fn main() void {
const T = struct {
id: u32,
name: []const u8,
active: bool,
};
const type_info = @typeInfo(T);
std.debug.print("Type: {}\n", .{type_info});
// 構造体の情報を取得
switch (type_info) {
.Struct => |struct_info| {
std.debug.print("Fields: {}\n", .{struct_info.fields.len});
inline for (struct_info.fields) |field| {
std.debug.print(" {s}: {}\n", .{field.name, field.type});
}
},
else => {},
}
}
型の動的検査
const std = @import("std");
fn hasField(comptime T: type, comptime field_name: []const u8) bool {
const type_info = @typeInfo(T);
switch (type_info) {
.Struct => |struct_info| {
inline for (struct_info.fields) |field| {
if (std.mem.eql(u8, field.name, field_name)) {
return true;
}
}
},
else => return false,
}
return false;
}
fn getFieldType(comptime T: type, comptime field_name: []const u8) type {
const type_info = @typeInfo(T);
switch (type_info) {
.Struct => |struct_info| {
inline for (struct_info.fields) |field| {
if (std.mem.eql(u8, field.name, field_name)) {
return field.type;
}
}
},
else => {},
}
@compileError("Field not found: " ++ field_name);
}
const User = struct {
id: u32,
name: []const u8,
email: []const u8,
};
pub fn main() void {
comptime {
std.debug.assert(hasField(User, "id"));
std.debug.assert(hasField(User, "name"));
std.debug.assert(!hasField(User, "age"));
}
const IdType = getFieldType(User, "id");
std.debug.print("id type: {}\n", .{IdType});
}
汎用シリアライザの実装
const std = @import("std");
fn serialize(writer: anytype, value: anytype) !void {
const T = @TypeOf(value);
const type_info = @typeInfo(T);
switch (type_info) {
.Int => {
try writer.print("{}", .{value});
},
.Float => {
try writer.print("{d}", .{value});
},
.Bool => {
try writer.print("{}", .{value});
},
.Pointer => |ptr_info| {
if (ptr_info.size == .Slice and ptr_info.child == u8) {
// 文字列
try writer.print("\"{s}\"", .{value});
} else {
return error.UnsupportedType;
}
},
.Struct => |struct_info| {
try writer.writeAll("{");
inline for (struct_info.fields, 0..) |field, i| {
if (i > 0) try writer.writeAll(",");
try writer.print("\"{s}\":", .{field.name});
try serialize(writer, @field(value, field.name));
}
try writer.writeAll("}");
},
.Array => |array_info| {
if (array_info.child == u8) {
// 文字列として扱う
try writer.print("\"{s}\"", .{value});
} else {
try writer.writeAll("[");
for (value, 0..) |item, i| {
if (i > 0) try writer.writeAll(",");
try serialize(writer, item);
}
try writer.writeAll("]");
}
},
else => {
return error.UnsupportedType;
},
}
}
const User = struct {
id: u32,
name: []const u8,
email: []const u8,
active: bool,
};
pub fn main() !void {
const user = User{
.id = 1,
.name = "Alice",
.email = "alice@example.com",
.active = true,
};
var buffer = std.ArrayList(u8).init(std.heap.page_allocator);
defer buffer.deinit();
try serialize(buffer.writer(), user);
std.debug.print("{s}\n", .{buffer.items});
}
メタプログラミングの実践
const std = @import("std");
// すべての数値型に対して動作する関数を生成
fn isNumeric(comptime T: type) bool {
return switch (@typeInfo(T)) {
.Int, .Float => true,
else => false,
};
}
fn add(a: anytype, b: anytype) @TypeOf(a, b) {
comptime {
if (!isNumeric(@TypeOf(a))) {
@compileError("Type must be numeric");
}
}
return a + b;
}
// 構造体の全フィールドをリセットする関数
fn reset(value: anytype) void {
const T = @TypeOf(value);
const type_info = @typeInfo(T);
switch (type_info) {
.Pointer => |ptr_info| {
if (ptr_info.size != .One) return;
const ChildInfo = @typeInfo(ptr_info.child);
switch (ChildInfo) {
.Struct => |struct_info| {
inline for (struct_info.fields) |field| {
@field(value, field.name) = switch (@typeInfo(field.type)) {
.Int => 0,
.Float => 0.0,
.Bool => false,
else => undefined,
};
}
},
else => {},
}
},
else => {},
}
}
const Stats = struct {
count: u32,
total: f64,
active: bool,
};
pub fn main() void {
std.debug.print("10 + 20 = {}\n", .{add(10, 20)});
std.debug.print("1.5 + 2.5 = {}\n", .{add(1.5, 2.5)});
var stats = Stats{
.count = 100,
.total = 500.0,
.active = true,
};
std.debug.print("Before: count={}, total={}, active={}\n", .{
stats.count, stats.total, stats.active
});
reset(&stats);
std.debug.print("After: count={}, total={}, active={}\n", .{
stats.count, stats.total, stats.active
});
}
学習ポイント:
-
@typeInfoで型の情報を取得 - 型の検査と検証
- コンパイル時に動作する汎用関数
- リフレクションによるメタプログラミング
4. build.zig 完全ガイド
ビルドシステムの基礎
// build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
// ターゲットとビルドモード
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// 実行ファイルの追加
const exe = b.addExecutable(.{
.name = "myapp",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// インストール
b.installArtifact(exe);
// runステップ
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
// コマンドライン引数を転送
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
// テスト
const tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const run_tests = b.addRunArtifact(tests);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_tests.step);
}
# ビルド
zig build
# 実行
zig build run
# テスト
zig build test
# リリースビルド
zig build -Doptimize=ReleaseFast
依存関係管理
// build.zig.zon
.{
.name = "myapp",
.version = "0.1.0",
.dependencies = .{
.clap = .{
.url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.9.1.tar.gz",
.hash = "122062d301a203d003547b414237229b09a7980095061697349f8bef41be9c30266b",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}
// build.zig(依存関係を使用)
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "myapp",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// 依存関係を追加
const clap = b.dependency("clap", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("clap", clap.module("clap"));
b.installArtifact(exe);
}
クロスコンパイル
// build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const optimize = b.standardOptimizeOption(.{});
// 複数のターゲット向けにビルド
const targets = [_]std.Target.Query{
.{ .cpu_arch = .x86_64, .os_tag = .linux },
.{ .cpu_arch = .x86_64, .os_tag = .windows },
.{ .cpu_arch = .x86_64, .os_tag = .macos },
.{ .cpu_arch = .aarch64, .os_tag = .linux },
.{ .cpu_arch = .aarch64, .os_tag = .macos },
};
for (targets) |target_query| {
const target = b.resolveTargetQuery(target_query);
const exe = b.addExecutable(.{
.name = "myapp",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const target_output = b.addInstallArtifact(exe, .{
.dest_dir = .{
.override = .{
.custom = try std.fmt.allocPrint(
b.allocator,
"{s}-{s}",
.{@tagName(target_query.cpu_arch.?), @tagName(target_query.os_tag.?)},
),
},
},
});
b.getInstallStep().dependOn(&target_output.step);
}
}
# すべてのターゲット向けにビルド
zig build
# 生成されたバイナリ
# zig-out/x86_64-linux/myapp
# zig-out/x86_64-windows/myapp.exe
# zig-out/x86_64-macos/myapp
# zig-out/aarch64-linux/myapp
# zig-out/aarch64-macos/myapp
カスタムビルドステップ
// build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "myapp",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// カスタムステップ: バージョン情報を生成
const gen_version = b.addSystemCommand(&[_][]const u8{
"git", "describe", "--tags", "--always",
});
const version_output = gen_version.captureStdOut();
const write_version = b.addWriteFiles();
_ = write_version.add("version.txt", version_output);
exe.step.dependOn(&write_version.step);
// カスタムステップ: コード生成
const codegen = b.addSystemCommand(&[_][]const u8{
"python3", "scripts/generate.py",
});
exe.step.dependOn(&codegen.step);
b.installArtifact(exe);
}
学習ポイント:
-
build.zigはZigで書かれたビルドスクリプト -
build.zig.zonで依存関係を管理 - クロスコンパイルが標準でサポート
- カスタムステップで柔軟なビルドプロセス
5. C FFI - 既存ライブラリの活用
@cImport と @cInclude
// C ヘッダーをインポート
const c = @cImport({
@cInclude("stdio.h");
@cInclude("stdlib.h");
@cInclude("string.h");
});
pub fn main() void {
// C の関数を呼び出す
_ = c.printf("Hello from C!\n");
// C のメモリ管理
const ptr = c.malloc(100);
defer c.free(ptr);
// C の文字列関数
const str1 = "Hello";
const str2 = "World";
const len = c.strlen(str1);
std.debug.print("Length: {}\n", .{len});
}
extern 関数の定義
const std = @import("std");
// C の関数を extern で宣言
extern "c" fn puts(s: [*:0]const u8) c_int;
extern "c" fn strlen(s: [*:0]const u8) usize;
extern "c" fn malloc(size: usize) ?*anyopaque;
extern "c" fn free(ptr: ?*anyopaque) void;
pub fn main() !void {
const message = "Hello from Zig!";
_ = puts(message);
const len = strlen(message);
std.debug.print("Length: {}\n", .{len});
}
C 構造体の扱い
const std = @import("std");
// C の構造体を定義
const CPoint = extern struct {
x: c_int,
y: c_int,
};
extern "c" fn process_point(point: *const CPoint) void;
// Zig から C へ
pub fn main() void {
var point = CPoint{ .x = 10, .y = 20 };
process_point(&point);
}
コールバック関数
const std = @import("std");
// C のコールバック型
const Callback = *const fn (c_int) callconv(.C) void;
extern "c" fn register_callback(callback: Callback) void;
extern "c" fn trigger_callback(value: c_int) void;
// Zig のコールバック関数
fn myCallback(value: c_int) callconv(.C) void {
std.debug.print("Callback called with: {}\n", .{value});
}
pub fn main() void {
register_callback(myCallback);
trigger_callback(42);
}
実例1: readline を使った対話的CLI
// build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "repl",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// readline をリンク
exe.linkSystemLibrary("readline");
exe.linkLibC();
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}
// src/main.zig
const std = @import("std");
const c = @cImport({
@cInclude("readline/readline.h");
@cInclude("readline/history.h");
});
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
std.debug.print("Simple REPL (type 'exit' to quit)\n", .{});
while (true) {
// readline で入力を取得
const line_ptr = c.readline("> ");
if (line_ptr == null) break;
defer c.free(line_ptr);
// C の文字列を Zig のスライスに変換
const line_len = c.strlen(line_ptr);
const line = line_ptr[0..line_len];
// 履歴に追加
c.add_history(line_ptr);
// コマンド処理
if (std.mem.eql(u8, line, "exit")) {
break;
} else if (std.mem.startsWith(u8, line, "echo ")) {
const msg = line[5..];
std.debug.print("{s}\n", .{msg});
} else if (std.mem.eql(u8, line, "help")) {
std.debug.print("Commands:\n", .{});
std.debug.print(" echo <message> - Echo a message\n", .{});
std.debug.print(" help - Show this help\n", .{});
std.debug.print(" exit - Exit the REPL\n", .{});
} else {
std.debug.print("Unknown command: {s}\n", .{line});
}
}
std.debug.print("Goodbye!\n", .{});
}
実例2: SDL2 でのゲーム開発の基礎
// build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "sdl_demo",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// SDL2 をリンク
exe.linkSystemLibrary("SDL2");
exe.linkLibC();
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}
// src/main.zig
const std = @import("std");
const c = @cImport({
@cInclude("SDL2/SDL.h");
});
pub fn main() !void {
// SDL の初期化
if (c.SDL_Init(c.SDL_INIT_VIDEO) != 0) {
std.debug.print("SDL_Init Error: {s}\n", .{c.SDL_GetError()});
return error.SDLInitFailed;
}
defer c.SDL_Quit();
// ウィンドウの作成
const window = c.SDL_CreateWindow(
"Zig + SDL2",
c.SDL_WINDOWPOS_CENTERED,
c.SDL_WINDOWPOS_CENTERED,
800,
600,
c.SDL_WINDOW_SHOWN,
) orelse {
std.debug.print("SDL_CreateWindow Error: {s}\n", .{c.SDL_GetError()});
return error.WindowCreateFailed;
};
defer c.SDL_DestroyWindow(window);
// レンダラーの作成
const renderer = c.SDL_CreateRenderer(window, -1, c.SDL_RENDERER_ACCELERATED) orelse {
std.debug.print("SDL_CreateRenderer Error: {s}\n", .{c.SDL_GetError()});
return error.RendererCreateFailed;
};
defer c.SDL_DestroyRenderer(renderer);
// メインループ
var running = true;
while (running) {
var event: c.SDL_Event = undefined;
// イベント処理
while (c.SDL_PollEvent(&event) != 0) {
switch (event.type) {
c.SDL_QUIT => running = false,
c.SDL_KEYDOWN => {
if (event.key.keysym.sym == c.SDLK_ESCAPE) {
running = false;
}
},
else => {},
}
}
// 画面クリア(黒)
_ = c.SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
_ = c.SDL_RenderClear(renderer);
// 矩形を描画(白)
_ = c.SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
const rect = c.SDL_Rect{ .x = 350, .y = 250, .w = 100, .h = 100 };
_ = c.SDL_RenderFillRect(renderer, &rect);
// 画面更新
c.SDL_RenderPresent(renderer);
// フレームレート制限
c.SDL_Delay(16); // 約60 FPS
}
}
実例3: C ライブラリのラッパー
// src/wrapper.zig
const std = @import("std");
const c = @cImport({
@cInclude("zlib.h");
});
pub const CompressionError = error{
InitFailed,
CompressFailed,
DecompressFailed,
};
pub fn compress(allocator: std.mem.Allocator, data: []const u8) ![]u8 {
const max_size = c.compressBound(@intCast(data.len));
const buffer = try allocator.alloc(u8, max_size);
errdefer allocator.free(buffer);
var dest_len: c_ulong = max_size;
const result = c.compress(
buffer.ptr,
&dest_len,
data.ptr,
@intCast(data.len),
);
if (result != c.Z_OK) {
allocator.free(buffer);
return CompressionError.CompressFailed;
}
return allocator.realloc(buffer, dest_len);
}
pub fn decompress(
allocator: std.mem.Allocator,
compressed: []const u8,
original_size: usize,
) ![]u8 {
const buffer = try allocator.alloc(u8, original_size);
errdefer allocator.free(buffer);
var dest_len: c_ulong = original_size;
const result = c.uncompress(
buffer.ptr,
&dest_len,
compressed.ptr,
@intCast(compressed.len),
);
if (result != c.Z_OK) {
allocator.free(buffer);
return CompressionError.DecompressFailed;
}
return buffer;
}
// 使用例
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const original = "Hello, Zig! This is a test string for compression.";
std.debug.print("Original: {s} ({} bytes)\n", .{original, original.len});
const compressed = try compress(allocator, original);
defer allocator.free(compressed);
std.debug.print("Compressed: {} bytes\n", .{compressed.len});
const decompressed = try decompress(allocator, compressed, original.len);
defer allocator.free(decompressed);
std.debug.print("Decompressed: {s}\n", .{decompressed});
}
学習ポイント:
-
@cImportで C ヘッダーをインポート -
externで C 関数を宣言 -
callconv(.C)で呼び出し規約を指定 - C ライブラリを Zig でラップして安全に使用
6. メタプログラミングとDSL
comptime によるDSL構築
const std = @import("std");
// SQL ライクなクエリDSL
fn Query(comptime T: type) type {
return struct {
const Self = @This();
table_name: []const u8,
where_clause: ?[]const u8 = null,
limit_value: ?usize = null,
pub fn init(table_name: []const u8) Self {
return Self{
.table_name = table_name,
};
}
pub fn where(self: Self, clause: []const u8) Self {
var new_self = self;
new_self.where_clause = clause;
return new_self;
}
pub fn limit(self: Self, value: usize) Self {
var new_self = self;
new_self.limit_value = value;
return new_self;
}
pub fn build(self: Self, allocator: std.mem.Allocator) ![]u8 {
var query = std.ArrayList(u8).init(allocator);
errdefer query.deinit();
try query.appendSlice("SELECT * FROM ");
try query.appendSlice(self.table_name);
if (self.where_clause) |clause| {
try query.appendSlice(" WHERE ");
try query.appendSlice(clause);
}
if (self.limit_value) |value| {
try query.appendSlice(" LIMIT ");
try std.fmt.format(query.writer(), "{}", .{value});
}
return query.toOwnedSlice();
}
};
}
const User = struct {
id: u32,
name: []const u8,
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const query = Query(User)
.init("users")
.where("age > 18")
.limit(10);
const sql = try query.build(allocator);
defer allocator.free(sql);
std.debug.print("SQL: {s}\n", .{sql});
// SQL: SELECT * FROM users WHERE age > 18 LIMIT 10
}
型安全なHTTPルーター
const std = @import("std");
const Method = enum {
GET,
POST,
PUT,
DELETE,
};
fn Route(comptime method: Method, comptime path: []const u8) type {
return struct {
const Self = @This();
pub const method_type = method;
pub const path_string = path;
handler: *const fn ([]const u8) []const u8,
pub fn init(handler: *const fn ([]const u8) []const u8) Self {
return Self{ .handler = handler };
}
pub fn handle(self: Self, body: []const u8) []const u8 {
return self.handler(body);
}
};
}
fn Router(comptime routes: anytype) type {
return struct {
const Self = @This();
routes_tuple: @TypeOf(routes),
pub fn init(routes_tuple: @TypeOf(routes)) Self {
return Self{ .routes_tuple = routes_tuple };
}
pub fn dispatch(
self: Self,
method: Method,
path: []const u8,
body: []const u8,
) ?[]const u8 {
inline for (self.routes_tuple) |route| {
const RouteType = @TypeOf(route);
if (RouteType.method_type == method and
std.mem.eql(u8, RouteType.path_string, path))
{
return route.handle(body);
}
}
return null;
}
};
}
// ハンドラ関数
fn getUsers(body: []const u8) []const u8 {
_ = body;
return "[{\"id\":1,\"name\":\"Alice\"}]";
}
fn createUser(body: []const u8) []const u8 {
std.debug.print("Creating user: {s}\n", .{body});
return "{\"id\":2,\"name\":\"Bob\"}";
}
pub fn main() void {
// ルートの定義
const routes = .{
Route(.GET, "/users").init(getUsers),
Route(.POST, "/users").init(createUser),
};
const router = Router(@TypeOf(routes)).init(routes);
// リクエストをディスパッチ
if (router.dispatch(.GET, "/users", "")) |response| {
std.debug.print("Response: {s}\n", .{response});
}
if (router.dispatch(.POST, "/users", "{\"name\":\"Bob\"}")) |response| {
std.debug.print("Response: {s}\n", .{response});
}
}
設定DSLの実装
const std = @import("std");
fn Config(comptime spec: anytype) type {
const fields = @typeInfo(@TypeOf(spec)).Struct.fields;
var struct_fields: [fields.len]std.builtin.Type.StructField = undefined;
inline for (fields, 0..) |field, i| {
const default_value = @field(spec, field.name);
struct_fields[i] = .{
.name = field.name,
.type = @TypeOf(default_value),
.default_value = &default_value,
.is_comptime = false,
.alignment = @alignOf(@TypeOf(default_value)),
};
}
return @Type(.{
.Struct = .{
.layout = .auto,
.fields = &struct_fields,
.decls = &.{},
.is_tuple = false,
},
});
}
pub fn main() void {
// 設定の定義
const ServerConfig = Config(.{
.host = "0.0.0.0",
.port = 8080,
.max_connections = 100,
.timeout_secs = 30,
.enable_logging = true,
});
// デフォルト値で初期化
const config = ServerConfig{};
std.debug.print("Host: {s}\n", .{config.host});
std.debug.print("Port: {}\n", .{config.port});
std.debug.print("Max connections: {}\n", .{config.max_connections});
// 一部だけ上書き
const custom_config = ServerConfig{
.port = 3000,
.max_connections = 500,
};
std.debug.print("Custom port: {}\n", .{custom_config.port});
}
学習ポイント:
-
comptimeでDSLを構築 - 型安全なAPI設計
- コンパイル時にコード生成
- 実行時オーバーヘッドなし
7. 最適化とパフォーマンスチューニング
リリースモードの違い
// build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
// 4つのビルドモード
const modes = [_]std.builtin.OptimizeMode{
.Debug, // デバッグ情報あり、最適化なし
.ReleaseSafe, // 最適化 + 安全性チェック
.ReleaseFast, // 最大限の最適化、安全性チェックなし
.ReleaseSmall, // サイズ優先の最適化
};
for (modes) |mode| {
const exe = b.addExecutable(.{
.name = std.fmt.comptimePrint("app_{s}", .{@tagName(mode)}),
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = mode,
});
b.installArtifact(exe);
}
}
# 各モードでビルド
zig build
# 生成されたバイナリのサイズを比較
ls -lh zig-out/bin/
Undefined Behavior (UB)
const std = @import("std");
pub fn main() void {
// Debug/ReleaseSafe: パニック
// ReleaseFast/ReleaseSmall: UB(未定義動作)
// 整数オーバーフロー
var x: u8 = 255;
x += 1; // Debug/ReleaseSafe でパニック
// 配列の範囲外アクセス
const array = [_]i32{ 1, 2, 3 };
const value = array[10]; // Debug/ReleaseSafe でパニック
// ゼロ除算
const result = 10 / 0; // Debug/ReleaseSafe でパニック
}
// 安全な代替
pub fn main() !void {
// ラップアラウンド演算
var x: u8 = 255;
x +%= 1; // 0 になる(UBではない)
// 範囲チェック
const array = [_]i32{ 1, 2, 3 };
const index: usize = 10;
const value = if (index < array.len) array[index] else return error.OutOfBounds;
// ゼロチェック
const divisor: i32 = 0;
const result = if (divisor != 0) @divTrunc(10, divisor) else return error.DivisionByZero;
}
プロファイリング
# Linux の場合: perf を使用
zig build -Doptimize=ReleaseFast
perf record ./zig-out/bin/myapp
perf report
# macOS の場合: Instruments を使用
zig build -Doptimize=ReleaseFast
instruments -t "Time Profiler" ./zig-out/bin/myapp
# Valgrind でメモリプロファイリング
valgrind --tool=callgrind ./zig-out/bin/myapp
ベンチマーク
const std = @import("std");
fn fibonacci_recursive(n: u32) u64 {
if (n <= 1) return n;
return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2);
}
fn fibonacci_iterative(n: u32) u64 {
if (n <= 1) return n;
var a: u64 = 0;
var b: u64 = 1;
var i: u32 = 2;
while (i <= n) : (i += 1) {
const temp = a + b;
a = b;
b = temp;
}
return b;
}
fn benchmark(comptime name: []const u8, comptime func: anytype, n: u32) !void {
const iterations = 1000;
var timer = try std.time.Timer.start();
const start = timer.read();
var i: usize = 0;
while (i < iterations) : (i += 1) {
_ = func(n);
}
const end = timer.read();
const elapsed_ns = end - start;
const avg_ns = elapsed_ns / iterations;
std.debug.print("{s}({}) avg: {} ns\n", .{name, n, avg_ns});
}
pub fn main() !void {
try benchmark("fibonacci_recursive", fibonacci_recursive, 20);
try benchmark("fibonacci_iterative", fibonacci_iterative, 20);
// 結果例:
// fibonacci_recursive(20) avg: 15234 ns
// fibonacci_iterative(20) avg: 42 ns
}
パフォーマンス最適化の実践
const std = @import("std");
// 遅い版: 頻繁なメモリ割り当て
fn processDataSlow(allocator: std.mem.Allocator, data: []const i32) ![]i32 {
var result = std.ArrayList(i32).init(allocator);
defer result.deinit();
for (data) |value| {
if (value > 0) {
try result.append(value * 2);
}
}
return result.toOwnedSlice();
}
// 速い版: 事前に容量を確保
fn processDataFast(allocator: std.mem.Allocator, data: []const i32) ![]i32 {
var result = try std.ArrayList(i32).initCapacity(allocator, data.len);
defer result.deinit();
for (data) |value| {
if (value > 0) {
result.appendAssumeCapacity(value * 2);
}
}
return result.toOwnedSlice();
}
// さらに速い版: インプレース処理
fn processDataInPlace(allocator: std.mem.Allocator, data: []const i32) ![]i32 {
const result = try allocator.alloc(i32, data.len);
var count: usize = 0;
for (data) |value| {
if (value > 0) {
result[count] = value * 2;
count += 1;
}
}
return allocator.realloc(result, count);
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// テストデータ
var data = try allocator.alloc(i32, 10000);
defer allocator.free(data);
var rng = std.rand.DefaultPrng.init(0);
for (data) |*value| {
value.* = rng.random().intRangeAtMost(i32, -100, 100);
}
// ベンチマーク
var timer = try std.time.Timer.start();
// 遅い版
const start1 = timer.read();
const result1 = try processDataSlow(allocator, data);
const end1 = timer.read();
allocator.free(result1);
// 速い版
const start2 = timer.read();
const result2 = try processDataFast(allocator, data);
const end2 = timer.read();
allocator.free(result2);
// さらに速い版
const start3 = timer.read();
const result3 = try processDataInPlace(allocator, data);
const end3 = timer.read();
allocator.free(result3);
std.debug.print("Slow: {} ns\n", .{end1 - start1});
std.debug.print("Fast: {} ns\n", .{end2 - start2});
std.debug.print("InPlace: {} ns\n", .{end3 - start3});
}
学習ポイント:
- Debug: 開発時、すべてのチェックあり
- ReleaseSafe: 本番、安全性チェックあり
- ReleaseFast: 本番、最大限の速度
- ReleaseSmall: 組み込み、最小サイズ
- UB を避けるか、意図的に使うか選択
- ベンチマークで測定してから最適化
8. 発展トピック: Inline Assembly
基本的なインラインアセンブリ
const std = @import("std");
// x86_64 での例
fn rdtsc() u64 {
var low: u32 = undefined;
var high: u32 = undefined;
asm volatile ("rdtsc"
: [low] "={eax}" (low),
[high] "={edx}" (high),
);
return (@as(u64, high) << 32) | low;
}
// I/O ポート操作(OS開発向け)
fn outb(port: u16, value: u8) void {
asm volatile ("outb %[value], %[port]"
:
: [value] "{al}" (value),
[port] "N{dx}" (port),
);
}
fn inb(port: u16) u8 {
return asm volatile ("inb %[port], %[result]"
: [result] "={al}" (-> u8),
: [port] "N{dx}" (port),
);
}
pub fn main() void {
const tsc = rdtsc();
std.debug.print("TSC: {}\n", .{tsc});
// 注意: 以下は特権モードでのみ動作
// outb(0x80, 0x42);
// const value = inb(0x80);
}
学習ポイント:
-
asm volatileでインラインアセンブリ - OS開発や特殊なハードウェア制御に使用
- 通常のアプリケーションでは不要
- 詳細: https://ziglang.org/documentation/master/#Assembly
次のステップ:
- OS開発に興味がある場合は専門書を参照
- Writing an OS in Zig
- OSDev Wiki
9. まとめ
Professional編で学んだこと
✅ カスタムアロケータ
- Allocator インターフェースの実装
- Arena、FixedBuffer、メモリプール
- メモリ管理の完全な制御
✅ comptime の高度な使い方
- 型レベルプログラミング
- コンパイル時コード生成
- 条件付きコンパイル
- 型安全なDSL構築
✅ @TypeInfo とリフレクション
- 型情報の取得と検査
- 汎用的なシリアライザ
- メタプログラミング
✅ build.zig
- ビルドシステムの完全制御
- 依存関係管理
- クロスコンパイル
- カスタムビルドステップ
✅ C FFI
- 既存ライブラリの活用
- readline、SDL2、zlib との連携
- 安全なラッパーの設計
✅ メタプログラミングとDSL
- SQLライクなクエリDSL
- 型安全なHTTPルーター
- 設定DSL
✅ 最適化とパフォーマンス
- リリースモードの使い分け
- UB の理解と回避
- プロファイリングとベンチマーク
Zigの哲学の実践
1. 明示的であること
// すべてが明示的
const buffer = try allocator.alloc(u8, 1024);
defer allocator.free(buffer);
2. 実行時コストの透明性
// comptimeはゼロコスト
comptime const size = calculate();
const Array = [size]i32;
3. C との相互運用性
// C ライブラリを簡単に使用
const c = @cImport({
@cInclude("stdio.h");
});
なぜZigか
vs C:
- より安全(コンパイル時チェック)
- より表現力が高い(comptime、ジェネリクス)
- より良いビルドシステム
vs C++:
- シンプル(隠れた制御フローなし)
- コンパイル時間が速い
- 学習曲線が緩やか
vs Rust:
- よりシンプル(所有権システムなし)
- C との相互運用が容易
- 明示的なメモリ管理
Zigが輝く場面:
- システムプログラミング
- 組み込みシステム
- C ライブラリの置き換え
- OS 開発
- 高性能アプリケーション
次のステップ
深く学ぶ:
実践的なプロジェクト:
- CLIツールの開発
- C ライブラリのラッパー
- 組み込みシステム開発
- ゲームエンジンの開発
コミュニティ: