Skip to content

Repository files navigation

Zimpl Zig interfaces

A dead simple implementation of static dispatch interfaces in Zig that emerged from a tiny subset of ztrait. See here for some motivation.

Also included is a compatible implementation of dynamic dispatch interfaces via comptime generated vtables. Inspired by interface.zig.

Warning: Zimpl was an experiment that I made for fun while learning Zig, I have never found a reason to use it for anything productive.

Static dispatch

Impl

pubfnImpl(comptimeIfc: fn (type) type, comptimeT: type) type { ... }

Definitions

If T is a single-item pointer type, then define U(T) to be the child type, i.e. T = *U(T), otherwise define U(T)=T.

Arguments

The function Ifc must always return a struct type. If U(T) has a declaration matching the name of a field from Ifc(T) that cannot coerce to the type of that field, then a compile error will occur (and a pretty good one now, thank you Zig Core Team).

Return value

The type Impl(Ifc, T) is a struct type with the same fields as Ifc(T), but with the default value of each field set equal to the declaration of U(T) of the same name, if such a declaration exists.

Example

// An interfacepubfnReader(comptimeT: type) type {
returnstruct {
ReadError: type=anyerror,
read: fn (reader_ctx: T, buffer: []u8) anyerror!usize,
};
}
// A collection of functions using the interfacepubconstio=struct {
pubinlinefnread(
reader_ctx: anytype,
reader_impl: Impl(Reader, @TypeOf(reader_ctx)),
buffer: []u8,
) reader_impl.ReadError!usize {
return@errorCast(reader_impl.read(reader_ctx, buffer));
}
pubinlinefnreadAll(
reader_ctx: anytype,
reader_impl: Impl(Reader, @TypeOf(reader_ctx)),
buffer: []u8,
) reader_impl.ReadError!usize {
returnreadAtLeast(reader_ctx, reader_impl, buffer, buffer.len);
}
pubinlinefnreadAtLeast(
reader_ctx: anytype,
reader_impl: Impl(Reader, @TypeOf(reader_ctx)),
buffer: []u8,
len: usize,
) reader_impl.ReadError!usize {
assert(len<=buffer.len);
varindex: usize=0;
while (index<len) {
constamt=tryread(reader_ctx, reader_impl, buffer[index..]);
if (amt==0) break;
index+=amt;
}
returnindex;
}
};
test"define and use a reader" {
constFixedBufferReader=struct {
buffer: []constu8,
pos: usize=0,
pubconstReadError=error{};
pubfnread(self: *@This(), out_buffer: []u8) ReadError!usize {
constlen=@min(self.buffer[self.pos..].len, out_buffer.len);
@memcpy(out_buffer[0..len], self.buffer[self.pos..][0..len]);
self.pos+=len;
returnlen;
}
};
constin_buf: []constu8="I really hope that this works!";
varreader=FixedBufferReader{ .buffer=in_buf };
varout_buf: [16]u8=undefined;
constlen=tryio.readAll(&reader, .{}, &out_buf);
trytesting.expectEqualStrings(in_buf[0..len], out_buf[0..len]);
}
test"use std.fs.File as a reader" {
varbuffer: [19]u8=undefined;
varfile=trystd.fs.cwd().openFile("my_file.txt", .{});
tryio.readAll(file, .{}, &buffer);
trystd.testing.expectEqualStrings("Hello, I am a file!", &buffer);
}
test"use std.os.fd_t as a reader via an explicitly defined interface" {
varbuffer: [19]u8=undefined;
constfd=trystd.os.open("my_file.txt", std.os.O.RDONLY, 0);
tryio.readAll(
fd,
.{ .read=std.os.read, .ReadError=std.os.ReadError, },
&buffer,
);
trystd.testing.expectEqualStrings("Hello, I am a file!", &buffer);
}

Dynamic dispatch

VIfc

pubfnVIfc(comptimeIfc: fn (type) type) type { ... }

Arguments

The Ifc function must always return a struct type.

Return value

Returns a struct of the following form:

struct {
ctx: *anyopaque,
vtable: VTable(Ifc),
pubfninit(
comptimeaccess: CtxAccess,
ctx: anytype,
impl: Impl(Ifc, CtxType(@TypeOf(ctx), access)),
) @This() {
return .{
.ctx=if (access==.indirect) @constCast(ctx) elsectx,
.vtable=vtable(Ifc, access, @TypeOf(ctx), impl),
};
}
};

The struct type VTable(Ifc) contains one field for each field of Ifc(*anyopaque) that is a (optional) function. The type of each vtable field is converted to a (optional) function pointer with the same signature.

The init function constructs a virtual interface from a given runtime context and interface implementation. Since the context is stored as a type-erased pointer, the access parameter is provided to allow vtables to be constructed for implementations that rely on non-pointer contexts.

pubconstCtxAccess=enum { direct, indirect };
fnCtxType(comptimeCtx: type, comptimeaccess: CtxAccess) type {
returnif (access==.indirect) @typeInfo(Ctx).Pointer.childelseCtx;
}

If access is .direct, then the type-erased ctx pointer stored in VIfc(Ifc) is cast as the correct pointer type and passed directly to concrete member function implementations.

Otherwise, if access is .indirect, ctx is a pointer to the actual context, and it is dereferenced and passed by value to member functions.

Example

// An interfacepubfnReader(comptimeT: type) type {
returnstruct {
// non-function fields are fine, but vtable interfaces ignore themReadError: type=anyerror,
read: fn (reader_ctx: T, buffer: []u8) anyerror!usize,
};
}
// A collection of functions using virtual 'Reader' interfacespubconstvio=struct {
pubinlinefnread(reader: VIfc(Reader), buffer: []u8) anyerror!usize {
returnreader.vtable.read(reader.ctx, buffer);
}
pubinlinefnreadAll(reader: VIfc(Reader), buffer: []u8) anyerror!usize {
returnreadAtLeast(reader, buffer, buffer.len);
}
pubfnreadAtLeast(
reader: VIfc(Reader),
buffer: []u8,
len: usize,
) anyerror!usize {
assert(len<=buffer.len);
varindex: usize=0;
while (index<len) {
constamt=tryread(reader, buffer[index..]);
if (amt==0) break;
index+=amt;
}
returnindex;
}
};
test"define and use a reader" {
constFixedBufferReader=struct {
buffer: []constu8,
pos: usize=0,
pubconstReadError=error{};
pubfnread(self: *@This(), out_buffer: []u8) ReadError!usize {
constlen=@min(self.buffer[self.pos..].len, out_buffer.len);
@memcpy(out_buffer[0..len], self.buffer[self.pos..][0..len]);
self.pos+=len;
returnlen;
}
};
constin_buf: []constu8="I really hope that this works!";
varreader=FixedBufferReader{ .buffer=in_buf };
varout_buf: [16]u8=undefined;
constlen=tryvio.readAll(Reader.init(.direct, &reader, .{}), &out_buf);
trytesting.expectEqualStrings(in_buf[0..len], out_buf[0..len]);
}
test"use std.fs.File as a reader" {
varbuffer: [19]u8=undefined;
varfile=trystd.fs.cwd().openFile("my_file.txt", .{});
tryvio.readAll(Reader.init(.indirect, &file, .{}), &buffer);
trystd.testing.expectEqualStrings("Hello, I am a file!", &buffer);
}
test"use std.os.fd_t as a reader via an explicitly defined interface" {
varbuffer: [19]u8=undefined;
constfd=trystd.os.open("my_file.txt", std.os.O.RDONLY, 0);
tryvio.readAll(
Reader.init(
.indirect,
&fd,
.{ .read=std.os.read, .ReadError=std.os.ReadError },
),
&buffer,
);
trystd.testing.expectEqualStrings("Hello, I am a file!", &buffer);
}

About

Simple comptime generic interfaces for Zig

Topics

Resources

Stars

57 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages