Ive heard it said that "[runtime polymorphism should be the responsibility of libraries]."
I understand how the common interface pattern work in the standard library and have implemented it a couple times myself. given how much code im repeating when i do this, im curious to explore having some type of helper function to create these interface.
heres a snippet for an example interface im working with:
const Interface = @This();
const std = @import("std");
ptr: *anyopaque,
_foo: *const fn (*anyopaque) void,
inline fn check_decl(comptime T: type, fn_name: []u8) void {
const pointed_type = @typeInfo(T).Pointer.child;
const valid = @hasDecl(pointed_type, fn_name);
if (!valid)
@compileError(@typeName(pointed_type) + " does not properly implement " + fn_name);
}
// this fn is generated at compile time because of anytype, which is a VALUE not a type.
pub fn init(implementor: anytype) Interface {
const ImplementorPointer = @TypeOf(implementor);
const type_info = @typeInfo(ImplementorPointer);
// General checks
if (type_info != .Pointer)
@compileError("Cannot initialize interface for non-pointer type");
if (type_info.Pointer.size != .One)
@compileError("Cannot initialize interface for multi-item pointer");
if (type_info.Pointer.is_const)
@compileError("Cannot initialize interface for non-mutable pointer");
if (@typeInfo(type_info.Pointer.child) != .Struct)
@compileError("Cannot initialize interface for pointer to non-struct");
// Check this type is valid as Interface (duck typing)
check_decl(ImplementorPointer, "foo");
const gen = struct {
pub fn _foo(impl: *anyopaque) void {
const self: ImplementorPointer = @ptrCast(@alignCast(impl));
type_info.Pointer.child.foo(self);
}
};
return .{
.ptr = implementor,
._foo = gen._foo,
};
}
pub fn foo(self: *Interface) void {
self._foo(self.ptr);
}
Id like to in some way, be able to have a generator function which creates structs at compile time for a given known interface. in mild pseudocode, something like this:
// a function which takes the requirements of the interface and creates
// a respective struct with an init fn and the proxy functions
fn create_interface(implementation_requirements) Interface {
return struct {
ptr: *anyopaque,
_[fn_name]: *[fn type]
pub fn init() { ... }
pub fn [fn_name] (self, [params]) [return type] {
return self._[fn_name]([params])
}
}
}
at first glance, there are many problems with making such a helper function. To me, chiefly among them are:
- creating a struct with fields of dynamic name.
- using a comptime loop of some effect to add fields to a struct.
- creating a struct with dynamic decls
- having those decls have comptime dynamic parameters and return types
Im looking for help if anyone has a good place to start, or information about why this isnt currently, or wont ever be supported.