Stricter function pointer casts

As an exercise I’m writing a wayland client implementation from scratch. Naturally this involves handling wayland events.

I currently have a system which involves storing a series of event handlers. These handlers have a context, which is not feasible to determine at comptime (primarily because this context can be any of a number of different types), so currently it is a *anyopaque. The struct which stores all these handlers looks something like the following:

struct {
    context: *anyopaque,
    call: *const fn (*anyopaque, u32, enum {}, []const u8) void,
}

I then call this with a code snippet similar to the following:

handler.call(handler.context, object, @enumFromInt(opcode), body);

My problem comes from the binding code itself. I end up with code that looks like the following:

self.handlers.items[wl_display].wl_display.set(.delete_id, .{
    .context = self,
    .call = @ptrCast(&Client.unbind),
});

With Client.unbind() defined as follows:

fn unbind(
    self: *Client,
    object: u32,
    opcode: wayland.display.event,
    body: []const u8,
) void

This is fine and works, however it does end up with type erasure of the function. That means if I were to forget one of the arguments for an event handler or accidentally get one of them swapped around, the code would still compile and simply end up with a nasty runtime error (I have had this happen in this specific case, fortunately it wasn’t too hard to find).

If I remove the @ptrCast(), I get the following compile error:

src/way2.zig:216:14: error: expected type '*const fn (*anyopaque, u32, protocols.wayland.display.event, []const u8) void', found '*const fn (*src.way2.Client, u32, protocols.wayland.display.event, []const u8) void'
            .call = &Client.unbind,
            ~^~~~~~~~~~~~~~~~~~~~~
src/way2.zig:216:14: note: pointer type child 'fn (*src.way2.Client, u32, protocols.wayland.display.event, []const u8) void' cannot cast into pointer type child 'fn (*anyopaque, u32, protocols.wayland.display.event, []const u8) void'
src/way2.zig:216:14: note: parameter 0 '*src.way2.Client' cannot cast into '*anyopaque'
src/way2.zig:216:14: note: pointer type child 'anyopaque' cannot cast into pointer type child 'src.way2.Client'

Ideally, I’d be able to remove the @ptrCast() to get stronger type guarantees, however it doesn’t seem possible to do that without changing the actual handling functions themselves to use anyopaque and use @ptrCast() within them, which I don’t like because I feel it impacts the readablity.

Is there another good workaround to get the strict compile time type checking?

Also it looks like the error message suffers from compile error hint for *anyopaque to *T due to function pointer parameter gets the type names backwards · Issue #13586 · ziglang/zig · GitHub

1 Like

I think this thread might help you: Callback with userdata Zig way