zig-comptime について
zig-comptime は、Zig 開発者向けにコンパイル時評価、ジェネリクス、型リフレクションを解説するスキルです。コンパイル時にコードを生成するパターンも紹介します。
主な機能
- 01comptime によるコンパイル時評価
- 02型パラメーターを使ったジェネリック関数
- 03anytype による引数型の推論
- 04@typeInfo による型のリフレクション
- 05コンパイル時のコード生成
活用シーン
- 01複数の型に対応する関数の作成
- 02型構造の検査と処理
- 03コンパイル時のルックアップテーブル生成
- 04マクロの一部をコンパイル時ロジックで置換
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mohitmishra786/ low-level-dev-skills --skill zig-comptimeインストール後、Claude Code で/zig-comptimeを入力して読み込まれたか確認します。
詳細
Zig comptime
目的
Zig の comptime システムについて、コンパイル時の関数評価、comptime 型パラメーター、anytype によるジェネリクス、@typeInfo による型のリフレクション、および C++ のテンプレートやマクロに代わるメタプログラミングのパターンをエージェントに案内します。
トリガー
- 「Zig の comptime はどのように動作しますか?」
- 「Zig でジェネリック関数を書くにはどうすればよいですか?」
- 「リフレクションに
@typeInfoを使うにはどうすればよいですか?」 - 「Zig でコンパイル時にコードを生成するにはどうすればよいですか?」
- 「Zig の
anytypeはどのように動作しますか?」 - 「Zig のジェネリクスは C++ のテンプレートとどう違いますか?」
ワークフロー
1. comptime の基本
// comptime keyword forces compile-time evaluation
const x: comptime_int = 42; // comptime integer (arbitrary precision)
const y: comptime_float = 3.14159; // comptime float (arbitrary precision)
// comptime block — runs at compile time
comptime {
const val = fibonacci(20); // computed at compile time
std.debug.assert(val == 6765); // compile-time assertion
}
// comptime parameter — caller must provide a comptime-known value
fn makeArray(comptime T: type, comptime n: usize) [n]T {
return [_]T{0} ** n; // array of n zeros of type T
}
const arr = makeArray(f32, 8); // [8]f32 computed at compile time
2. comptime 型パラメーターを使ったジェネリック関数
const std = @import("std");
// Generic max function — T must be comptime-known
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
// Usage: T is inferred from arguments or specified explicitly
const r1 = max(i32, 3, 7); // 7
const r2 = max(f64, 2.5, 1.8); // 2.5
// Generic Stack data structure
fn Stack(comptime T: type) type {
return struct {
items: []T,
top: usize,
allocator: std.mem.Allocator,
const Self = @This();
pub fn init(allocator: std.mem.Allocator) !Self {
return Self{
.items = try allocator.alloc(T, 64),
.top = 0,
.allocator = allocator,
};
}
pub fn push(self: *Self, value: T) void {
self.items[self.top] = value;
self.top += 1;
}
pub fn pop(self: *Self) ?T {
if (self.top == 0) return null;
self.top -= 1;
return self.items[self.top];
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.items);
}
};
}
// Usage: Stack(i32) and Stack(f64) are distinct types
var int_stack = try Stack(i32).init(allocator);
defer int_stack.deinit();
int_stack.push(42);
3. anytype — ダックタイピングされた comptime パラメーター
anytype は任意の型を受け取り、コンパイラーは呼び出し箇所で型を推論します。
// anytype: function works for any type with .len field
fn printLength(thing: anytype) void {
std.debug.print("Length: {}\n", .{thing.len});
}
printLength("hello"); // string literal — works
printLength([_]u8{1, 2, 3}); // array — works
printLength(std.ArrayList(u32){}); // ArrayList — works
// anytype with comptime checks for better errors
fn serialize(writer: anytype, value: anytype) !void {
// Verify writer has write method at comptime
if (!@hasDecl(@TypeOf(writer), "write")) {
@compileError("writer must have a write method");
}
try writer.write(std.mem.asBytes(&value));
}
// anytype in struct methods (used throughout std library)
pub fn format(
self: MyType,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype, // any writer: file, buffer, etc.
) !void {
try writer.print("{} {}", .{self.x, self.y});
}
4. @typeInfo による型のリフレクション
@typeInfo は、comptime 時に型の構造を表すタグ付きユニオンを返します。
const std = @import("std");
const TypeInfo = std.builtin.Type;
fn printTypeInfo(comptime T: type) void {
const info = @typeInfo(T);
switch (info) {
.Int => |i| std.debug.print("Int: {} bits, {s}\n",
.{i.bits, @tagName(i.signedness)}),
.Float => |f| std.debug.print("Float: {} bits\n", .{f.bits}),
.Struct => |s| {
std.debug.print("Struct with {} fields:\n", .{s.fields.len});
inline for (s.fields) |field| {
std.debug.print(" {s}: {}\n", .{field.name, field.type});
}
},
.Enum => |e| {
std.debug.print("Enum with {} values:\n", .{e.fields.len});
inline for (e.fields) |field| {
std.debug.print(" {s} = {}\n", .{field.name, field.value});
}
},
.Optional => |o| std.debug.print("Optional({s})\n", .{@typeName(o.child)}),
.Array => |a| std.debug.print("[{}]{s}\n", .{a.len, @typeName(a.child)}),
else => std.debug.print("Other type: {s}\n", .{@typeName(T)}),
}
}
// Usage at comptime
comptime { printTypeInfo(u32); } // Int: 32 bits, unsigned
comptime { printTypeInfo(f64); } // Float: 64 bits
5. comptime でコードを生成するパターン
// Generate a lookup table at comptime
const sin_table = blk: {
const N = 256;
var table: [N]f32 = undefined;
@setEvalBranchQuota(10000); // increase for expensive comptime eval
for (0..N) |i| {
const angle = @as(f32, @floatFromInt(i)) * (2.0 * std.math.pi / N);
table[i] = @sin(angle);
}
break :blk table;
};
// Comptime string processing
fn upperCase(comptime s: []const u8) [s.len]u8 {
var result: [s.len]u8 = undefined;
for (s, 0..) |c, i| {
result[i] = std.ascii.toUpper(c);
}
return result;
}
const HELLO = upperCase("hello"); // computed at compile time
// Structural typing: accept any struct with specific fields
fn area(shape: anytype) f64 {
const T = @TypeOf(shape);
if (@hasField(T, "width") and @hasField(T, "height")) {
return @as(f64, shape.width) * @as(f64, shape.height);
} else if (@hasField(T, "radius")) {
return std.math.pi * @as(f64, shape.radius) * @as(f64, shape.radius);
} else {
@compileError("shape must have width+height or radius fields");
}
}
6. comptime と C++ テンプレートの比較
| 機能 | C++ テンプレート | Zig comptime |
|---|---|---|
| 構文 | template<typename T> | fn foo(comptime T: type) |
| エラーメッセージ | 難解なインスタンス化のスタック | 定義箇所で明確に表示 |
| 特殊化 | template<> class Foo<int> | inline if を使った if (T == i32) { ... } |
| SFINAE | 複雑な enable_if | @hasDecl、@hasField、@compileError |
| 可変長引数 | template<typename... Ts> | anytype、タプル、inline for |
| コンパイル時間 | 非常に遅くなる場合がある | 明示的で、@setEvalBranchQuota によって上限を設定 |
| 値 | constexpr が必要 | 任意の式を comptime にできる |
| マクロ | 独立した #define システム | comptime 関数がほとんどのマクロに取って代わる |
7. よく使われる comptime のパターン
// Pattern: compile error for unsupported types
fn serializeInt(comptime T: type, value: T) []const u8 {
if (@typeInfo(T) != .Int) {
@compileError("serializeInt requires an integer type, got: " ++ @typeName(T));
}
// ...
}
// Pattern: conditional compilation
const is_debug = @import("builtin").mode == .Debug;
if (comptime is_debug) {
// included only in debug builds
validateInvariant(self);
}
// Pattern: inline for over comptime-known slice
const fields = std.meta.fields(MyStruct);
inline for (fields) |field| {
// field.name, field.type available at comptime
std.debug.print("{s}\n", .{field.name});
}
関連スキル
- comptime アサーションと comptime コードのテストには、
skills/zig/zig-testingを使用してください - comptime ベースの build.zig 設定には、
skills/zig/zig-build-systemを使用してください - C++ テンプレートに相当するパターンには、
skills/compilers/cpp-templatesを使用してください
コメント
Markdown 対応