# interfaces > written against zig 0.15. zig has no interfaces or traits. use comptime patterns instead. ## anytype with validation accept `anytype` and validate at comptime: ```zig fn process(db: anytype) !void { const T = @TypeOf(db); if (!@hasDecl(T, "exec")) { @compileError("db must have exec method"); } try db.exec("SELECT 1", .{}); } ``` this is the simplest approach - compiler verifies the type has required methods. ## type-returning functions for generic wrappers, return a type that delegates to an implementation: ```zig pub fn DatabaseInterface(comptime Impl: type) type { return struct { impl: Impl, const Self = @This(); pub fn exec(self: Self, sql: []const u8, args: anytype) !void { return self.impl.exec(sql, args); } }; } ``` useful when you want a consistent outer API regardless of implementation. ## vtables for runtime dispatch when you need runtime polymorphism (rare in zig): ```zig pub const Database = struct { ptr: *anyopaque, vtable: *const VTable, const VTable = struct { exec: *const fn (*anyopaque, []const u8) anyerror!void, }; pub fn exec(self: Database, sql: []const u8) !void { return self.vtable.exec(self.ptr, sql); } }; ``` this is the std.io.Writer pattern. more boilerplate, but allows swapping implementations at runtime. ## comptime-parameterized adapter when 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: ```zig fn WsHandler(comptime H: type) type { return struct { allocator: Allocator, handler: *H, client_state: *FirehoseClient, const Self = @This(); pub fn serverMessage(self: *Self, data: []const u8) !void { var arena = std.heap.ArenaAllocator.init(self.allocator); defer arena.deinit(); const event = decodeFrame(arena.allocator(), data) catch return; self.handler.onEvent(event); } pub fn close(_: *Self) void {} }; } ``` this 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. see: [zat/firehose.zig](https://tangled.sh/@zzstoatzz.io/zat/tree/main/src/internal/firehose.zig) (zat is a library of atproto building blocks for zig) ## practical: module boundary pattern for most apps, just separate interface documentation from implementation: ``` db/ mod.zig -- re-exports, expected methods documented in comments sqlite.zig -- sqlite implementation ``` the "interface" is implicit - documented expectations in `mod.zig`, verified by usage at compile time. see: [comptime](./comptime.md), [database](./database.md) ## sources - zat — src/internal/firehose.zig