Zig NEWS

Val
Val

Posted on

Impl on Userland is quite easy, actually

Generics are cool. But I dont like to rely on duck typing when I am reading code, its messy. What are the function signatures I should be following? Is there any generic function that is expected to be used there?

Playing around with @yaksher with how semantic we could make generics, we ended up with a simple pattern, that achieves 3 things:

  1. No runtime costs;
  2. Call site has total control on how the functions behave, as long as they have the expected function signature;
  3. Library checks if the interface was used.

So, sily example with a IWriter interface that checks if a type is a Writer:

const std = @import("std");

// Interface that we will implement on callsite and verify usage on library
pub const IWriter = struct {
    const name = "IWriter";
    fn is(comptime T: type) bool {
        return @hasDecl(T, "isIWriter") and T.isIWriter == IWriter;
    }
    // specify what the implementation entails here
    // depending on the usecase, you could have wrapper functions specified here instead of a simple assignment
    pub fn impl(comptime T: type, comptime Impl: type) type {
        return struct {
            const isIWriter = IWriter;
            const writeAll: fn (T, []const u8) anyerror!void = Impl.writeAll;
        };
    }
};

pub fn implements(comptime T: type, comptime I: type) void {
    if (!I.is(T)) {
        @compileError("Type does not implement `" ++ I.name ++ "`. (tip: wrap your instance with [your impl of "++ I.name ++ "](...).");
    }
}

// library function that verifies if the interface was used
pub fn write(w: anytype, data: []const u8) !void {
    comptime implements(@TypeOf(w), IWriter); // comptime, otherwise all the compile errors of how `w` misses the decls will also show up
    try w.writeAll(data);
}

// Callsite usage of how to create a type that implements an interface
const FileWriter = struct {
    writer: std.fs.File.Writer,
    usingnamespace IWriter.impl(FileWriter, struct {
        fn writeAll(self: FileWriter, data: []const u8) anyerror!void {
            try self.writer.writeAll(data);
        }
    });
};

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    const writer = FileWriter{ .writer = stdout };
    try write(writer, "Hello world!");
}
Enter fullscreen mode Exit fullscreen mode

And just like that, you get the most convoluted "Hello World" I ever written.

Top comments (1)

Collapse
 
voilaneighbor profile image
Fifnmar

That's a brilliant idea