-
-
Save boomlinde/e157735ec612784f91e14fe36518bfcf to your computer and use it in GitHub Desktop.
This file contains 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| { | |
const method = @field(Impl, field.name); | |
if (!sigEql(field.type, @TypeOf(method))) | |
@compileError("Invalid method signature, expected: " ++ | |
@typeName(field.type) ++ | |
", found: " ++ | |
@typeName(@TypeOf(method))); | |
const func_ptr: *const anyopaque = @ptrCast(&method); | |
@field(out, field.name) = @ptrCast(func_ptr); | |
} | |
break :blk out; | |
}; | |
}; | |
return &VTableContainer.vt; | |
} | |
fn sigEql(comptime func0: type, comptime func1: type) bool { | |
const info0 = getBuiltinFn(func0) orelse | |
@compileError("Expected function, found: " ++ | |
@typeName(func0)); | |
const info1 = getBuiltinFn(func1) orelse | |
@compileError("Expected function, found: " ++ | |
@typeName(func0)); | |
if (info0.params.len != info1.params.len) return false; | |
if (!isSelfPtr(info0.params[0].type.?) and !isSelfPtr(info1.params[0].type.?)) | |
return false; | |
for (info0.params[1..], info1.params[1..]) |p0, p1| { | |
if (p0.type != p1.type) return false; | |
} | |
return true; | |
} | |
inline fn isSelfPtr(comptime T: type) bool { | |
return switch (@typeInfo(T)) { | |
.Pointer => |ptr| if (ptr.size != .One) false else true, | |
else => false, | |
}; | |
} | |
inline fn getBuiltinFn(comptime T: type) ?std.builtin.Type.Fn { | |
return switch (@typeInfo(T)) { | |
.Fn => |f| f, | |
.Pointer => |ptr| switch (@typeInfo(ptr.child)) { | |
.Fn => |f| f, | |
else => null, | |
}, | |
else => null, | |
}; | |
} | |
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