Zig C Callbacks
A C callback is a Zig function handed to C code so C code can call it later. The function must use the C calling convention so C code can call it correctly.
Sort with a callback
The C sort function orders an array by calling the callback for pairs of items. The callback compares two integers and reports their order.
const std = @import("std");
const c = @cImport({
@cInclude("stdlib.h");
});
fn compare(a: ?*const anyopaque, b: ?*const anyopaque) callconv(.C) c_int {
const x: *const c_int = @ptrCast(@alignCast(a.?));
const y: *const c_int = @ptrCast(@alignCast(b.?));
if (x.* < y.*) return -1;
if (x.* > y.*) return 1;
return 0;
}
pub fn main() !void {
var arr = [_]c_int{ 5, 2, 8, 1 };
c.qsort(&arr, arr.len, @sizeOf(c_int), compare);
const out = std.io.getStdOut().writer();
try out.print("{d} {d} {d} {d}\n", .{ arr[0], arr[1], arr[2], arr[3] });
}
Sort in reverse
Swapping the return values reverses the order. The same sort function now places the largest value first.
const std = @import("std");
const c = @cImport({
@cInclude("stdlib.h");
});
fn compare(a: ?*const anyopaque, b: ?*const anyopaque) callconv(.C) c_int {
const x: *const c_int = @ptrCast(@alignCast(a.?));
const y: *const c_int = @ptrCast(@alignCast(b.?));
if (x.* < y.*) return 1;
if (x.* > y.*) return -1;
return 0;
}
pub fn main() !void {
var arr = [_]c_int{ 5, 2, 8, 1 };
c.qsort(&arr, arr.len, @sizeOf(c_int), compare);
const out = std.io.getStdOut().writer();
try out.print("{d} {d} {d} {d}\n", .{ arr[0], arr[1], arr[2], arr[3] });
}
A callback value on its own
A callback can also be stored in a variable and called directly. The variable holds the function, and calling it doubles the input.
const std = @import("std");
const Fn = *const fn (c_int) callconv(.C) c_int;
fn double(x: c_int) callconv(.C) c_int {
return x * 2;
}
pub fn main() !void {
const f: Fn = double;
const out = std.io.getStdOut().writer();
try out.print("doubled={d}\n", .{f(21)});
}
Example
const std = @import("std");
const c = @cImport({
@cInclude("stdlib.h");
});
fn compare(a: ?*const anyopaque, b: ?*const anyopaque) callconv(.C) c_int {
const x: *const c_int = @ptrCast(@alignCast(a.?));
const y: *const c_int = @ptrCast(@alignCast(b.?));
if (x.* < y.*) return -1;
if (x.* > y.*) return 1;
return 0;
}
pub fn main() !void {
var arr = [_]c_int{ 9, 3, 7 };
c.qsort(&arr, arr.len, @sizeOf(c_int), compare);
const out = std.io.getStdOut().writer();
try out.print("{d} {d} {d}\n", .{ arr[0], arr[1], arr[2] });
}
Key points
- Callback: a callback is a function passed to another function so it can be called later.
- Calling convention: a calling convention is the agreed way a function receives arguments and returns a value.
- The callconv marker on a Zig function selects the C calling convention for that function.
- A sorting function from C takes a callback that compares two items and reports their order.
- The callback receives pointers to the items, reads both values, and returns a negative, zero, or positive number.
- The -lc flag makes the C library available to every block in this lesson.
The example defines a compare function with the C calling convention. It hands the function to the C sort function together with the array. The sort function calls the callback as needed, and the program prints the sorted array.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
What must a Zig function use so C code can call it as a callback?