I’m interacting with a C library where the main method of communicating with the library is through user-specified callback functions. Of course, in Zig, writing something like this is fairly easy
const c = @cImport({
@cInclude("api/foo.h");
@cInclude("api/bar.h");
};
fn fooCallback(
args: ?[*:0]const u8,
context: ?*anyopaque,
) callconv(.c) c_int {
// Implementation
}
fn barCallback(
args: ?[*:0]const u8,
context: ?*anyopaque,
) callconv(.c) c_int {
// Implementation
}
// snip
var foo_context: FooCallbackContext = .init();
c.api_register_foo_callback(fooCallback, &foo_context);
var bar_context: BarCallbackContext = .init();
c.api_register_bar_callback(barCallback, &bar_context);
//etc etc
Now I’m left with the problem that writing those callback functions is a bit of a pain because of the type restriction on the function pointer.
Within the function, every error union needs to be handled and turned into an appropriate c_int value, and that adds a lot of noise to unhappy paths in code that is already very noise-prone:
const result = try failable(arg1, arg2);
becomes something like
const result = failable(arg1, arg2) catch |err| return switch |err| {
error.case1 => c.api_case1,
error.case2 => c.api_case2,
error.case3 => c.api_case_not_quite_3_but_close_enough_I_guess,
else => c.api_case_unexpected,
};
Are there any good patterns for handling this kind of thing?