about things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

interfaces#

zig has no interfaces or traits. use comptime patterns instead.

anytype with validation#

accept anytype and validate at comptime:

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:

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):

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:

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

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, database