Zig comptime
Contract
| Field | Bound contract |
|---|---|
| Trigger | The user writes a generic Zig function or type, asks how comptime or anytype works, needs reflection over a type's fields, wants a table or string computed at compile time, or is porting template metaprogramming from C++. |
| Authority | Read-only. The skill emits Zig code and readings to chat; the user writes them. Rollback is not needed. No remote mutation. |
| Side effect | Chat output; scratch compiles in a scratch directory. |
| Done | The comptime construct for the request is reported and a scratch file using it compiles and runs on the installed Zig. |
Inputs
- Zig version from
zig version: required. Samples ran on Zig 0.14.1, where@typeInfotags are lowercase (.int,.@"struct"); older releases used capitalized tags. - The function, type, or value the user wants computed or generic: required.
- Whether the result must exist at compile time (table, type) or only be generic (function over
T): required.
Procedure
State the basics.
comptime_intandcomptime_floatare arbitrary precision and exist only at compile time. Acomptime { ... }block runs during compilation;std.debug.assertinside it is a compile-time check. Acomptime T: typeparameter must be known at the call site:fn makeArray(comptime T: type, comptime n: usize) [n]T { return [_]T{0} ** n; }. Compile-time evaluation is bounded by a branch quota; raise it with@setEvalBranchQuota(N)inside the evaluating scope when a recursive or long loop exceeds the default 1000 backward branches. Done when: the user knows which values are comptime-known.Write generic functions and types. A function over a type takes
comptime T: type:fn max(comptime T: type, a: T, b: T) T. A generic type is a function returningtype: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 .{ .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 deinit(self: *Self) void { self.allocator.free(self.items); } }; }Stack(i32)andStack(f64)are distinct types, memoized per argument. Done when: the generic compiles for two argument types.Use
anytypefor duck-typed parameters. The type is inferred per call site:fn printLength(thing: anytype) void { std.debug.print("{}\n", .{thing.len}); }works for a string literal, an array, or a slice. Guard it for a clear error:if (!@hasDecl(@TypeOf(writer), "write")) @compileError("writer must have a write method");. The standard library's writer parameters follow this pattern. Done when: everyanytypeparameter has the operations it needs, checked or documented.Reflect with
@typeInfo, which returns a tagged unionstd.builtin.Typeat comptime. Switch on it with the lowercase tags:.int => |i| i.bits, i.signedness,.float => |f| f.bits,.@"struct" => |s| s.fields,.@"enum" => |e| e.fields,.optional => |o| o.child,.array => |a| a.len, a.child. Iterate fields withinline for (s.fields) |field|;std.meta.fields(T)is the shorthand. Reject unsupported types withif (@typeInfo(T) != .int) @compileError("requires an integer type, got: " ++ @typeName(T));. Done when: the reflection covers every tag the code can meet or ends inelse.Build data at compile time. A lookup table is a labeled block:
const table = blk: { var t: [256]f32 = undefined; @setEvalBranchQuota(10000); for (0..256) |i| t[i] = @sin(@as(f32, @floatFromInt(i)) * (2.0 * std.math.pi / @as(f32, 256))); break :blk t; };. Mixedcomptime_floatandcomptime_intdivision must be made explicit with@as, or the compiler reports an ambiguous coercion. A comptime string transform returns[s.len]u8from acomptime s: []const u8parameter. Structural typing checks fields with@hasField(T, "width")and falls back to@compileError. Done when: the table or string is aconstand the build proves it evaluates.Map C++ template idioms when porting. Done when: each idiom in the request has its Zig form.
C++ Zig template<typename T>fn f(comptime T: type)Specialization template<> class Foo<int>if (T == i32) { ... }inside the type function, at comptimeSFINAE and enable_if@hasDecl,@hasField,@typeInfo,@compileErrorVariadic templates anytypetuples andinline forconstexprAny expression evaluated in a comptimecontextMacros Comptime functions Apply the recurring patterns: conditional compilation with
const is_debug = @import("builtin").mode == .Debug;andif (comptime is_debug);inline forover comptime-known slices to unroll per field. Done when: no runtime branch depends on a value known at compile time.Confirm with a scratch file run by
zig run scratch.zigon the installed Zig. Done when: it prints the expected values.
Failure and recovery
| Failure class | Behavior |
|---|---|
evaluation exceeded 1000 backwards branches |
Add @setEvalBranchQuota(N) in the scope that performs the evaluation, not in the callee. |
ambiguous coercion of division operands |
Cast one operand with @as(f32, ...) or @as(f64, ...). |
Capitalized @typeInfo tags rejected |
The installed Zig uses lowercase tags; rewrite .Int as .int, .Struct as .@"struct". |
@compileError fires from an anytype guard |
The call site passed a type without the required decl or field; the message names the missing one. |
| Test of a comptime function wanted | Comptime asserts inside test blocks: use zig-testing. |
Output
A chat report with the comptime code for the request, the reading of any reflection or coercion error, and the scratch-run line showing it compiled and ran on the installed Zig.