Zig bindings for c closures

I’m trying to make these c functions easier to use:

pub extern fn z_closure_sample(
    this_: [*c]struct_z_owned_closure_sample_t,
    call: ?*const fn ([*c]struct_z_loaned_sample_t, ?*anyopaque) callconv(.c) void,
    drop: ?*const fn (?*anyopaque) callconv(.c) void,
    context: ?*anyopaque,
) void;

pub extern fn z_declare_subscriber(
    session: [*c]const struct_z_loaned_session_t,
    subscriber: [*c]struct_z_owned_subscriber_t,
    key_expr: [*c]const struct_z_loaned_keyexpr_t,
    callback: [*c]struct_z_moved_closure_sample_t,
    options: [*c]struct_z_subscriber_options_t,
) z_result_t;

Here is what the unimproved example code looks like (without any improved bindings):

var got_message: bool = false;

fn data_handler(sample: [*c]zenoh.c.z_loaned_sample_t, arg: ?*anyopaque) callconv(.c) void {
    _ = sample;
    _ = arg;
    std.log.info("Got sample!", .{});
    got_message = true;
}

fn subscribe() !void {
    var config: zenoh.c.z_owned_config_t = undefined;
    _ = zenoh.c.z_config_default(&config);
    defer zenoh.c.z_config_drop(zenoh.c.z_config_move(&config));

    var options: zenoh.c.z_open_options_t = undefined;
    zenoh.c.z_open_options_default(&options);
    var session: zenoh.c.z_owned_session_t = undefined;
    if (zenoh.c.z_open(&session, zenoh.c.z_config_move(&config), &options) != 0) {
        std.log.err("Failed to open zenoh session.", .{});
        return error.OpenSessionFailure;
    }
    defer zenoh.c.z_session_drop(zenoh.c.z_session_move(&session));

    var callback: zenoh.c.z_owned_closure_sample_t = undefined;
    zenoh.c.z_closure_sample(&callback, &data_handler, null, null);

    var key_expr: zenoh.c.z_view_keyexpr_t = undefined;
    _ = zenoh.c.z_view_keyexpr_from_str(&key_expr, "key/expression");

    var subscriber: zenoh.c.z_owned_subscriber_t = undefined;

    if (zenoh.c.z_declare_subscriber(
        zenoh.c.z_session_loan_mut(&session),
        &subscriber,
        zenoh.c.z_view_keyexpr_loan(&key_expr),
        zenoh.c.z_closure_sample_move(&callback),
        null,
    ) != 0) {
        std.log.err("Failed to create zenoh subscriber", .{});
        return error.DeclareSubscriberFailure;
    }
    defer zenoh.c.z_subscriber_drop(zenoh.c.z_subscriber_move(&subscriber));

    var timer = std.time.Timer.start() catch @panic("timer unsupported");

    while (timer.read() <= std.time.ns_per_s * 10) {
        if (got_message) {
            break;
        }
        std.Thread.sleep(std.time.ns_per_s * 1);
    } else {
        return error.NoMessage;
    }
}

How do I write bindings for registering a callback with a c function?

I have so far been constructing wrapper types on the c types:

pub const Config = struct {
    _c: c.z_owned_config_t,

    pub fn initDefault() Error!Config {
        var c_config: c.z_owned_config_t = undefined;
        try err(c.z_config_default(&c_config));
        return Config{ ._c = c_config };
    }
}

The docs for the function are:

void z_closure_sample(struct z_owned_closure_sample_t *this_, void (*call)(struct z_loaned_sample_t *sample, void *context), void (*drop)(void *context), void *context)

    Constructs closure.

    Closures are not guaranteed not to be called concurrently.

    It is guaranteed that:

        call will never be called once drop has started.

        drop will only be called once, and after every call has ended.

        The two previous guarantees imply that call and drop are never called concurrently.

    Parameters:

            this_ – uninitialized memory location where new closure will be constructed.

            call – a closure body.

            drop – an optional function to be called once on closure drop.

            context – closure context.

  1. How can I “register a callback” with the C API so that it returns a zig type?
  2. Does anyone have any examples of bindings that register callbacks with c functions?
  3. Why is there a drop callback?
  1. depends, do you want a direct mapping just zigified? do you want to do something more fancy?
  2. idk
  3. so you can do something when the closure is done being used, since you won’t have much control over that

yes, several of projects i’ve used or contributed to register callbacks. with this level of detail (not a lot) it’s hard for me to say what you’re doing back to you.

here are some links:

pushing a function to the Lua environment from Zig. (note the zlua.wrap call, which transforms a Zig callback function into a callconv(.c) function with the correct type; if you’re writing bindings, it might be interesting to investigate that.)

here is my Zig interface to libobjc’s implementation of “blocks”, the Objective-C version of closures, with an example of how to use it from Zig:

1 Like

Here is a binding for GObject Closure. You may want to check it out.

    /// ctor [new](https://docs.gtk.org/gtk4/ctor.CustomFilter.new.html)
    pub fn new(match_func: anytype, match_func_args: anytype) *CustomFilter {
        var closure_match_func = core.ZigClosure.newWithContract(match_func, match_func_args, fn (*gobject.Object) bool);
        const _match_func: ?gtk.CustomFilterFunc = @ptrCast(closure_match_func.cCallback());
        const _user_data: ?*anyopaque = @ptrCast(closure_match_func.cData());
        const _user_destroy: glib.DestroyNotify = @ptrCast(closure_match_func.cDestroy());
        const cFn = @extern(*const fn (?gtk.CustomFilterFunc, ?*anyopaque, glib.DestroyNotify) callconv(.c) *CustomFilter, .{ .name = "gtk_custom_filter_new" });
        const ret = cFn(_match_func, @ptrCast(_user_data), _user_destroy);
        return ret;
    }

Honestly I’m having trouble understanding what a closure even is.

This looks like a good intro: Closures in Zig

Theoretically, “A function that carries with it its enclosing local scope.”

If your language can do something like

pub fn makeCounter(x: i64) fn () i64 {
    var count: i64 = 0;
    fn increment() i64 {
        count += x;
        return count;
    }
    return increment;
}

const fiver = makeCounter( 5 ); //makeCounter returns a function that increments 'count' in its local scope by 5.
std.debug.print("{d}\n", .{ fiver.increment() }); //prints 5
std.debug.print("{d}\n", .{ fiver.increment() }); //prints 10
std.debug.print("{d}\n", .{ fiver.increment() }); //prints 15

const twoer = makeCounter( 2 );
std.debug.print("{d}\n", .{ twoer.increment() }); //prints 2
std.debug.print("{d}\n", .{ twoer.increment() }); //prints 4
std.debug.print("{d}\n", .{ twoer.increment() }); //prints 6

Then it has closures.

Zig or C doesn’t have closures, so you need a data structure to store the environment you want to “capture” and a function to mutate it, if you want to implement a closure.