Function signature compatibility wrt. anon struct return type

I was trying to implement an interface with a function that returns an anonymous struct, and ran into a problem, I recreated a simple code example that hopefully shows the problem:

pub const VTable = struct {
    myFun: *const fn (self: *anyopaque) struct { x: u32, y: u32 },
};

pub const MyInterface = struct {
    ptr: *anyopaque,
    vtable: *const VTable,

    pub fn myfun(self: *MyInterface) struct { x: u32, y: u32 } {
        return self.vtable.myFun(@ptrCast(self));
    }
};

pub const MyStruct = struct {
    pub fn asMyInterface(self: *MyStruct) MyInterface {
        return .{
            .ptr = self,
            .vtable = &.{
                .myFun = self.myFun,
            },
        };
    }

    pub fn myfun(self: *anyopaque) struct { x: u32, y: u32 } {
        _ = self;
        return .{ .x = 10, .y = 10 };
    }
};

pub fn main() !void {
    var myStruct: MyStruct = .{};
    var myStructAsMyInterface: MyInterface = myStruct.asMyInterface();

    const retval = myStructAsMyInterface.myfun();

    std.debug.print("{} {}\n", .{ retval.x, retval.y });
}

Here is the compiler’s complaint as far as I understand: it does not think the anonymous structs are the same type. Here is the compiler’s error message:

src/main.zig:29:33: error: expected type 'main.MyInterface.myfun__struct_22452', found 'main.VTable__struct_22444'
        return self.vtable.myFun(@ptrCast(self));
               ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~
src/main.zig:21:41: note: struct declared here
    myFun: *const fn (self: *anyopaque) struct { x: u32, y: u32 },
                                        ^~~~~~~~~~~~~~~~~~~~~~~~~
src/main.zig:28:38: note: struct declared here
    pub fn myfun(self: *MyInterface) struct { x: u32, y: u32 } {
                                     ^~~~~~~~~~~~~~~~~~~~~~~~~
src/main.zig:28:38: note: function return type declared here
referenced by:
    main: src/main.zig:53:47
    callMain [inlined]: /home/oskar/.cache/zig/p/N-V-__8AAN5NhBR0oTsvnwjPdeNiiDLtEsfXRHd1fv-R3TOv/lib/std/start.zig:627:37
    callMainWithArgs [inlined]: /home/oskar/.cache/zig/p/N-V-__8AAN5NhBR0oTsvnwjPdeNiiDLtEsfXRHd1fv-R3TOv/lib/std/start.zig:587:20
    posixCallMainAndExit: /home/oskar/.cache/zig/p/N-V-__8AAN5NhBR0oTsvnwjPdeNiiDLtEsfXRHd1fv-R3TOv/lib/std/start.zig:542:36
    2 reference(s) hidden; use '-freference-trace=6' to see all references
src/main.zig:38:31: error: no field named 'myFun' in struct 'main.MyStruct'
                .myFun = self.myFun,
                              ^~~~~
src/main.zig:33:22: note: struct declared here
pub const MyStruct = struct {
                     ^~~~~~

I am basically asking if things are supposed to work this way.

Note: I am on zig 15.1 & 15.2.

Normal zig struct’s have an explicitly undefined layout, even types that are defined the exact same are not guaranteed to have the same layout. The exception to this are tuples, structs whose fields have no name* e.g. struct {u32, u32}. Just have a pub const MyFunStruct = struct { x: u32, y: u32}; and use that, explicit structs are preferable over tuples.

You are trying to access a declaration on an instance, access it from the type instead MyStruct.myFun/@This().myFun

It’s not shown in the error because zig trusts you, but

@ptrCast(self) for the vtable functions’ data ptr is just wrong, do self.ptr instead. zig will eventually catch this, it’s just not implemented yet.

2 Likes