Skip to content

Instantly share code, notes, and snippets.

@boomlinde
Created January 9, 2024 19:39
Show Gist options
  • Save boomlinde/760ccfdf4deb2e7852ed792653a48773 to your computer and use it in GitHub Desktop.
Save boomlinde/760ccfdf4deb2e7852ed792653a48773 to your computer and use it in GitHub Desktop.
const std = @import("std");
// Returns an instance of Interface wrapped around the implementation
// at ptr
pub fn as(comptime Interface: type, ptr: anytype) Interface {
return .{
.ptr = @ptrCast(@alignCast(ptr)),
.vtable = vtable(Interface.VTable, @TypeOf(ptr.*)),
};
}
// Given a VTable and an implementation type Impl, return
// a pointer to a static populated VTable corresponding to the implementation
pub fn vtable(comptime VTable: type, comptime Impl: type) *const VTable {
const fields = switch (@typeInfo(VTable)) {
.Struct => |f| f.fields,
else => @compileError("Can't populate non-struct vtable"),
};
const VTableContainer = struct {
const vt = blk: {
var out: VTable = undefined;
for (fields) |field| {
@field(out, field.name) = OpaqueWrapper(@field(Impl, field.name)).wrapper;
}
break :blk out;
};
};
return &VTableContainer.vt;
}
// Given a method, return its corresponding type erased wrapper
// This is a terrible hack, and only works for methods with up
// to 10 parameters (including self).
fn OpaqueWrapper(comptime method: anytype) type {
switch (@typeInfo(@TypeOf(method))) {
.Fn => |f| {
const Ret = f.return_type.?;
return switch (f.params.len) {
1 => Wrappers.w1(
method,
f.params[0].type.?,
Ret,
),
2 => Wrappers.w2(
method,
f.params[0].type.?,
f.params[1].type.?,
Ret,
),
3 => Wrappers.w3(
method,
f.params[0].type.?,
f.params[1].type.?,
f.params[2].type.?,
Ret,
),
4 => Wrappers.w4(
method,
f.params[0].type.?,
f.params[1].type.?,
f.params[2].type.?,
f.params[3].type.?,
Ret,
),
5 => Wrappers.w5(
method,
f.params[0].type.?,
f.params[1].type.?,
f.params[2].type.?,
f.params[3].type.?,
f.params[4].type.?,
Ret,
),
6 => Wrappers.w6(
method,
f.params[0].type.?,
f.params[1].type.?,
f.params[2].type.?,
f.params[3].type.?,
f.params[4].type.?,
f.params[5].type.?,
Ret,
),
7 => Wrappers.w7(
method,
f.params[0].type.?,
f.params[1].type.?,
f.params[2].type.?,
f.params[3].type.?,
f.params[4].type.?,
f.params[5].type.?,
f.params[6].type.?,
Ret,
),
8 => Wrappers.w8(
method,
f.params[0].type.?,
f.params[1].type.?,
f.params[2].type.?,
f.params[3].type.?,
f.params[4].type.?,
f.params[5].type.?,
f.params[6].type.?,
f.params[7].type.?,
Ret,
),
9 => Wrappers.w9(
method,
f.params[0].type.?,
f.params[1].type.?,
f.params[2].type.?,
f.params[3].type.?,
f.params[4].type.?,
f.params[5].type.?,
f.params[6].type.?,
f.params[7].type.?,
f.params[8].type.?,
Ret,
),
10 => Wrappers.w10(
method,
f.params[0].type.?,
f.params[1].type.?,
f.params[2].type.?,
f.params[3].type.?,
f.params[4].type.?,
f.params[5].type.?,
f.params[6].type.?,
f.params[7].type.?,
f.params[8].type.?,
f.params[9].type.?,
Ret,
),
else => @compileError("Too many method parameters"),
};
},
else => @compileError("Wrappers.wrap only works on functions"),
}
}
const Wrappers = struct {
fn w1(
comptime method: anytype,
comptime SelfPtr: type,
comptime Ret: type,
) type {
return struct {
fn wrapper(ptr: *anyopaque) Ret {
const self: SelfPtr = @ptrCast(@alignCast(ptr));
return method(self);
}
};
}
fn w2(
comptime method: anytype,
comptime SelfPtr: type,
comptime T1: type,
comptime Ret: type,
) type {
return struct {
fn wrapper(ptr: *anyopaque, a: T1) Ret {
const self: SelfPtr = @ptrCast(@alignCast(ptr));
return method(self, a);
}
};
}
fn w3(
comptime method: anytype,
comptime SelfPtr: type,
comptime T1: type,
comptime T2: type,
comptime Ret: type,
) type {
return struct {
fn wrapper(ptr: *anyopaque, a: T1, b: T2) Ret {
const self: SelfPtr = @ptrCast(@alignCast(ptr));
return method(self, a, b);
}
};
}
fn w4(
comptime method: anytype,
comptime SelfPtr: type,
comptime T1: type,
comptime T2: type,
comptime T3: type,
comptime Ret: type,
) *const fn (anytype, T1, T2, T3) Ret {
return (struct {
fn wrapper(ptr: *anyopaque, a: T1, b: T2, c: T3) Ret {
const self: SelfPtr = @ptrCast(@alignCast(ptr));
return method(self, a, b, c);
}
}).wrapper;
}
fn w5(
comptime method: anytype,
comptime SelfPtr: type,
comptime T1: type,
comptime T2: type,
comptime T3: type,
comptime T4: type,
comptime Ret: type,
) *const fn (anytype, T1, T2, T3, T4) Ret {
return (struct {
fn wrapper(ptr: *anyopaque, a: T1, b: T2, c: T3, d: T4) Ret {
const self: SelfPtr = @ptrCast(@alignCast(ptr));
return method(self, a, b, c, d);
}
}).wrapper;
}
fn w6(
comptime method: anytype,
comptime SelfPtr: type,
comptime T1: type,
comptime T2: type,
comptime T3: type,
comptime T4: type,
comptime T5: type,
comptime Ret: type,
) *const fn (anytype, T1, T2, T3, T4, T5) Ret {
return (struct {
fn wrapper(ptr: *anyopaque, a: T1, b: T2, c: T3, d: T4, e: T5) Ret {
const self: SelfPtr = @ptrCast(@alignCast(ptr));
return method(self, a, b, c, d, e);
}
}).wrapper;
}
fn w7(
comptime method: anytype,
comptime SelfPtr: type,
comptime T1: type,
comptime T2: type,
comptime T3: type,
comptime T4: type,
comptime T5: type,
comptime T6: type,
comptime Ret: type,
) *const fn (anytype, T1, T2, T3, T4, T5, T6) Ret {
return (struct {
fn wrapper(ptr: *anyopaque, a: T1, b: T2, c: T3, d: T4, e: T5, f: T6) Ret {
const self: SelfPtr = @ptrCast(@alignCast(ptr));
return method(self, a, b, c, d, e, f);
}
}).wrapper;
}
fn w8(
comptime method: anytype,
comptime SelfPtr: type,
comptime T1: type,
comptime T2: type,
comptime T3: type,
comptime T4: type,
comptime T5: type,
comptime T6: type,
comptime T7: type,
comptime Ret: type,
) *const fn (anytype, T1, T2, T3, T4, T5, T6, T7) Ret {
return (struct {
fn wrapper(ptr: *anyopaque, a: T1, b: T2, c: T3, d: T4, e: T5, f: T6, g: T7) Ret {
const self: SelfPtr = @ptrCast(@alignCast(ptr));
return method(self, a, b, c, d, e, f, g);
}
}).wrapper;
}
fn w9(
comptime method: anytype,
comptime SelfPtr: type,
comptime T1: type,
comptime T2: type,
comptime T3: type,
comptime T4: type,
comptime T5: type,
comptime T6: type,
comptime T7: type,
comptime T8: type,
comptime Ret: type,
) *const fn (anytype, T1, T2, T3, T4, T5, T6, T7, T8) Ret {
return (struct {
fn wrapper(ptr: *anyopaque, a: T1, b: T2, c: T3, d: T4, e: T5, f: T6, g: T7, h: T8) Ret {
const self: SelfPtr = @ptrCast(@alignCast(ptr));
return method(self, a, b, c, d, e, f, g, h);
}
}).wrapper;
}
fn w10(
comptime method: anytype,
comptime SelfPtr: type,
comptime T1: type,
comptime T2: type,
comptime T3: type,
comptime T4: type,
comptime T5: type,
comptime T6: type,
comptime T7: type,
comptime T8: type,
comptime T9: type,
comptime Ret: type,
) *const fn (anytype, T1, T2, T3, T4, T5, T6, T7, T8, T9) Ret {
return (struct {
fn wrapper(ptr: *anyopaque, a: T1, b: T2, c: T3, d: T4, e: T5, f: T6, g: T7, h: T8, i: T9) Ret {
const self: SelfPtr = @ptrCast(@alignCast(ptr));
return method(self, a, b, c, d, e, f, g, h, i);
}
}).wrapper;
}
};
test "Example" {
const Greeter = struct {
// Still need to declare a type erased VTable manually
pub const VTable = struct {
hello: *const fn (*anyopaque, i32) []const u8,
};
ptr: *anyopaque,
vtable: *const VTable,
pub fn init(ptr: anytype) @This() {
// No need to manually wrap implementation methods
return as(@This(), ptr);
}
// Still need to wrap the VTable functions to expose as methods
// on the interface
pub fn hello(self: @This(), i: i32) []const u8 {
return self.vtable.hello(self.ptr, i);
}
};
const ExampleGreeter = struct {
buf: []u8,
pub fn hello(self: *const @This(), i: i32) []const u8 {
return std.fmt.bufPrint(self.buf, "Hello, {}", .{i}) catch "Hello";
}
};
const OtherExampleGreeter = struct {
buf: []u8,
pub fn hello(self: *const @This(), i: i32) []const u8 {
return std.fmt.bufPrint(self.buf, "Hello, {}", .{-i}) catch "Hello";
}
};
var buf: [20]u8 = undefined;
var impl1 = ExampleGreeter{ .buf = &buf };
var impl2 = OtherExampleGreeter{ .buf = &buf };
const g1 = Greeter.init(&impl1);
const g2 = Greeter.init(&impl2);
try std.testing.expect(Greeter == @TypeOf(g1));
try std.testing.expect(Greeter == @TypeOf(g2));
try std.testing.expectEqualStrings("Hello, 5", g1.hello(5));
try std.testing.expectEqualStrings("Hello, -5", g2.hello(5));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment