permutationlock / zimpl

Simple comptime generic interfaces for Zig

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.

Static dispatch

Impl

pub fn Impl(comptime Ifc: fn (type) type, comptime T: type) type { ... }

Definitions

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

Arguments

The function Ifc must always return a struct type. If U 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.

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 of the same name, if such a declaration exists.

Example

// An interface
pub fn Reader(comptime T: type) type {
    return struct {
        ReadError: type = anyerror,
        read: fn (reader_ctx: T, buffer: []u8) anyerror!usize,
    };
}

// A collection of functions using the interface
pub const io = struct {
    pub inline fn read(
        reader_ctx: anytype,
        reader_impl: Impl(Reader, @TypeOf(reader_ctx)),
        buffer: []u8,
    ) reader_impl.ReadError!usize {
        return @errorCast(reader_impl.read(reader_ctx, buffer));
    }

    pub inline fn readAll(
        reader_ctx: anytype,
        reader_impl: Impl(Reader, @TypeOf(reader_ctx)),
        buffer: []u8,
    ) reader_impl.ReadError!usize {
        return readAtLeast(reader_ctx, reader_impl, buffer, buffer.len);
    }

    pub inline fn readAtLeast(
        reader_ctx: anytype,
        reader_impl: Impl(Reader, @TypeOf(reader_ctx)),
        buffer: []u8,
        len: usize,
    ) reader_impl.ReadError!usize {
        assert(len <= buffer.len);
        var index: usize = 0;
        while (index < len) {
            const amt = try read(reader_ctx, reader_impl, buffer[index..]);
            if (amt == 0) break;
            index += amt;
        }
        return index;
    }
};

test "define and use a reader" {
    const FixedBufferReader = struct {
        buffer: []const u8,
        pos: usize = 0,

        pub const ReadError = error{};

        pub fn read(self: *@This(), out_buffer: []u8) ReadError!usize {
            const len = @min(self.buffer[self.pos..].len, out_buffer.len);
            @memcpy(out_buffer[0..len], self.buffer[self.pos..][0..len]);
            self.pos += len;
            return len;
        }
    };
    const in_buf: []const u8 = "I really hope that this works!";
    var reader = FixedBufferReader{ .buffer = in_buf };

    var out_buf: [16]u8 = undefined;
    const len = try io.readAll(&reader, .{}, &out_buf);

    try testing.expectEqualStrings(in_buf[0..len], out_buf[0..len]);
}

test "use std.fs.File as a reader" {
    var buffer: [19]u8 = undefined;
    var file = try std.fs.cwd().openFile("my_file.txt", .{});
    try io.readAll(file, .{}, &buffer);

    try std.testing.expectEqualStrings("Hello, I am a file!", &buffer);
}

test "use std.os.fd_t as a reader via an explicitly defined interface" {
    var buffer: [19]u8 = undefined;
    const fd = try std.os.open("my_file.txt", std.os.O.RDONLY, 0);
    try io.readAll(
        fd,
        .{ .read = std.os.read, .ReadError = std.os.ReadError, },
        &buffer,
    );

    try std.testing.expectEqualStrings("Hello, I am a file!", &buffer);
}

Dynamic dispatch

VIfc

pub fn VIfc(comptime Ifc: 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),
};

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.

makeVIfc

pub fn makeVIfc(
    comptime Ifc: fn (type) type,
) fn (CtxAccess, anytype, anytype) VIfc(Ifc) { ... }

Arguments

The Ifc function must always return a struct type.

Return value

Returns a function to construct a VIfc(Ifc) vtable interface from a concrete runtime context and corresponding interface implementation. The returned function has the the following signature.

fn (
    comptime access: CtxAccess,
    ctx: anytype,
    impl: Impl(Ifc, CtxType(@TypeOf(ctx), access)),
) VIfc(Ifc)

Since vtable interfaces store their context as a type-erased pointer, the access parameter is provided to allow vtables to be constructed for implementations that rely on non-pointer contexts.

pub const CtxAccess = enum { Direct, Indirect };

fn CtxType(comptime Ctx: type, comptime access: CtxAccess) type {
    return if (access == .Indirect) @typeInfo(Ctx).Pointer.child else Ctx;
}

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 interface
pub fn Reader(comptime T: type) type {
    return struct {
        // non-function fields are fine, but vtable interfaces ignore them
        ReadError: type = anyerror,
        read: fn (reader_ctx: T, buffer: []u8) anyerror!usize,
    };
}

// Function to construct a virtual 'Reader' interface implementation
const makeReader = makeVIfc(Reader);

// A collection of functions using virtual 'Reader' interfaces
pub const vio = struct {
    pub inline fn read(reader: VIfc(Reader), buffer: []u8) anyerror!usize {
        return reader.vtable.read(reader.ctx, buffer);
    }

    pub inline fn readAll(reader: VIfc(Reader), buffer: []u8) anyerror!usize {
        return readAtLeast(reader, buffer, buffer.len);
    }

    pub fn readAtLeast(
        reader: VIfc(Reader),
        buffer: []u8,
        len: usize,
    ) anyerror!usize {
        assert(len <= buffer.len);
        var index: usize = 0;
        while (index < len) {
            const amt = try read(reader, buffer[index..]);
            if (amt == 0) break;
            index += amt;
        }
        return index;
    }
};

test "define and use a reader" {
    const FixedBufferReader = struct {
        buffer: []const u8,
        pos: usize = 0,

        pub const ReadError = error{};

        pub fn read(self: *@This(), out_buffer: []u8) ReadError!usize {
            const len = @min(self.buffer[self.pos..].len, out_buffer.len);
            @memcpy(out_buffer[0..len], self.buffer[self.pos..][0..len]);
            self.pos += len;
            return len;
        }
    };
    const in_buf: []const u8 = "I really hope that this works!";
    var reader = FixedBufferReader{ .buffer = in_buf };

    var out_buf: [16]u8 = undefined;
    const len = try vio.readAll(makeReader(.Direct, &reader, .{}), &out_buf);

    try testing.expectEqualStrings(in_buf[0..len], out_buf[0..len]);
}

test "use std.fs.File as a reader" {
    var buffer: [19]u8 = undefined;
    var file = try std.fs.cwd().openFile("my_file.txt", .{});
    try vio.readAll(makeReader(.Indirect, &file, .{}), &buffer);

    try std.testing.expectEqualStrings("Hello, I am a file!", &buffer);
}

test "use std.os.fd_t as a reader via an explicitly defined interface" {
    var buffer: [19]u8 = undefined;
    const fd = try std.os.open("my_file.txt", std.os.O.RDONLY, 0);
    try vio.readAll(
        makeReader(
            .Indirect,
            &fd,
            .{
                .read = std.os.read,
                .ReadError = std.os.ReadError,
            },
        ),
        &buffer,
    );

    try std.testing.expectEqualStrings("Hello, I am a file!", &buffer);
}

About

Simple comptime generic interfaces for Zig

License:MIT License


Languages

Language:Zig 100.0%