Hi everyone! I’m trying to implement a simple interface (for learning purposes) named Shape1
which will hopefully allow me to iterate over different shapes generically and execute shapes’ area()
methods accordingly:
const Circle = struct {
radius: f32,
fn area(self: *anyopaque) f32 {
const s: *const Circle = @ptrCast(@alignCast(self));
return std.math.pi * s.radius * s.radius;
}
};
const Square = struct {
side: f32,
fn area(self: *anyopaque) f32 {
const s: *const Square = @ptrCast(@alignCast(self));
return s.side * s.side;
}
};
const Shape1 = struct {
ptr: *anyopaque,
vtab: *const VTab,
const VTab = struct {
areaFn: *const fn (ptr: *anyopaque) f32,
};
fn area(self: *Shape1) f32 {
return self.vtab.areaFn(self.ptr);
}
fn init(ptr: *anyopaque, f: *const fn (ptr: *anyopaque) f32) Shape1 {
return .{
.ptr = ptr,
.vtab = &VTab{ .areaFn = f },
};
}
};
pub fn main() !void {
var sq = Square{ .side = 2 };
var cr = Circle{ .radius = 2 };
var shapes = [_]Shape1{
Shape1.init(&sq, Square.area),
Shape1.init(&cr, Circle.area),
};
for (&shapes) |*shape| {
std.log.debug("{d}", .{shape.area()});
}
}
First, I was trying to use ptr: *const anyopaque
and it succeeded. Then, I switched to non-const pointers and now code segfaults. Stepping in the debugger showed it happens as soon as I access shape
in the loop. Tbh, I’m not sure why. All the things reside on the stack and the accessing them shouldn’t be a problem.
I tried to get rid of &Vtable {...}
in init
by simply inlining areaFn
into Shape1
itself. And it worked! Segfault disappeared. However, in the repo explaining different approaches to implementing interface (here), the author uses &VTab{}
in the same init()
successfully, without using any allocator.
What do I do wrong?