-
-
Save boomlinde/760ccfdf4deb2e7852ed792653a48773 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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