Hi @LoicCode, welcome to Ziggit!
I tried your second code example and depending on the Zig version I get different results.
On 0.15.2, I get a segfault. On 0.16.0-dev.1976+8e091047b it runs fine.
I think you are running into some illegal behavior that just "happenss’ to work fine depending on struct sizes and the stack.
That being said, I don’t think you want to go down this route.
This is probably part that is causing the issue. You stack allocate an implementation and the return a value that points at the stack. This becomes invalid memory after get_default returns.
Typically (in the std lib anyway), you want your concrete implementation to create the interface, not the other way around. So you want your DefaultScheduler to have a method that returns a Scheduler interface, not the other way around. The Scheduler interface needs an internal pointer, and you can’t get it from a function. You could, however, use a global constant for the scheduler for the default. This has it’s own caveats for use (may not be thread safe).
Here’s how I would try to rewrite it:
const std = @import("std");
pub const Scheduler = struct {
ptr: *anyopaque,
vtab: *const struct {
start: *const fn (ptr: *anyopaque) void,
stop: *const fn (ptr: *anyopaque) void,
},
pub fn start(self: Scheduler) void {
self.vtab.start(self.ptr);
}
pub fn stop(self: Scheduler) void {
self.vtab.stop(self.ptr);
}
};
const DefaultScheduler = struct {
name: []const u8,
pub fn start(ptr: *anyopaque) void {
const self: *DefaultScheduler = @ptrCast(@alignCast(ptr));
std.debug.print("Hidden start implementation: {s} \n", .{self.name});
}
pub fn stop(ptr: *anyopaque) void {
const self: *DefaultScheduler = @ptrCast(@alignCast(ptr));
const n = self.name;
std.debug.print("Hidden stop implementation: {s} \n", .{n});
}
fn init() DefaultScheduler {
return .{ .name = "DefaultScheduler" };
}
pub fn scheduler(self: *DefaultScheduler) Scheduler {
return .{
.ptr = self,
.vtab = &.{
.start = start,
.stop = stop,
},
};
}
};
pub fn main() void {
var default = DefaultScheduler.init();
const sched = default.scheduler();
sched.start();
sched.stop();
}
You’ll notice that the Default scheduler knows how to conform to the scheduler interface. You’ll see this pattern in the allocators (DebugAllocator.allocator) and readers and writers (File.reader).