about things
1# interfaces
2
3zig has no interfaces or traits. use comptime patterns instead.
4
5## anytype with validation
6
7accept `anytype` and validate at comptime:
8
9```zig
10fn process(db: anytype) !void {
11 const T = @TypeOf(db);
12 if (!@hasDecl(T, "exec")) {
13 @compileError("db must have exec method");
14 }
15 try db.exec("SELECT 1", .{});
16}
17```
18
19this is the simplest approach - compiler verifies the type has required methods.
20
21## type-returning functions
22
23for generic wrappers, return a type that delegates to an implementation:
24
25```zig
26pub fn DatabaseInterface(comptime Impl: type) type {
27 return struct {
28 impl: Impl,
29
30 const Self = @This();
31
32 pub fn exec(self: Self, sql: []const u8, args: anytype) !void {
33 return self.impl.exec(sql, args);
34 }
35 };
36}
37```
38
39useful when you want a consistent outer API regardless of implementation.
40
41## vtables for runtime dispatch
42
43when you need runtime polymorphism (rare in zig):
44
45```zig
46pub const Database = struct {
47 ptr: *anyopaque,
48 vtable: *const VTable,
49
50 const VTable = struct {
51 exec: *const fn (*anyopaque, []const u8) anyerror!void,
52 };
53
54 pub fn exec(self: Database, sql: []const u8) !void {
55 return self.vtable.exec(self.ptr, sql);
56 }
57};
58```
59
60this is the std.io.Writer pattern. more boilerplate, but allows swapping implementations at runtime.
61
62## comptime-parameterized adapter
63
64when you need a struct that bridges between a library's callback interface and a user's handler type, return a type parameterized on the handler:
65
66```zig
67fn WsHandler(comptime H: type) type {
68 return struct {
69 allocator: Allocator,
70 handler: *H,
71 client_state: *FirehoseClient,
72
73 const Self = @This();
74
75 pub fn serverMessage(self: *Self, data: []const u8) !void {
76 var arena = std.heap.ArenaAllocator.init(self.allocator);
77 defer arena.deinit();
78
79 const event = decodeFrame(arena.allocator(), data) catch return;
80 self.handler.onEvent(event);
81 }
82
83 pub fn close(_: *Self) void {}
84 };
85}
86```
87
88this adapts the websocket library's `serverMessage` callback to our firehose handler's `onEvent` interface. the comptime parameter `H` is the user's handler type — the compiler generates a specialized struct for each handler type used. no vtables, no allocation, full inlining.
89
90see: [zat/firehose.zig](https://tangled.sh/@zzstoatzz.io/zat/tree/main/src/internal/firehose.zig)
91
92## practical: module boundary pattern
93
94for most apps, just separate interface documentation from implementation:
95
96```
97db/
98 mod.zig -- re-exports, expected methods documented in comments
99 sqlite.zig -- sqlite implementation
100```
101
102the "interface" is implicit - documented expectations in `mod.zig`, verified by usage at compile time.
103
104see: [comptime](./comptime.md), [database](./database.md)