I have a C interface and want to make a thin zero-cost wrapper. Tbh I even have a solution but it doesn’t seem idiomatic zig for me (even though I obviously don’t know how idiomatic zig looks like because I am a beginner). So I want to show you my solution and ask you how you would do it
//====== What I have in C ======
// u32 is a handle type
extern fn createDevice(...) u32;
extern fn destroyDevice(handle: u32) void;
extern fn getLed(handle: u32, led_handle: u32) ;
extern fn getDeviceProperty(handle u32, ...) <...>;
//====== What I want ==========
device = Device.createDevice(); // This one is optional
device.getLed(...);
device.getDeviceProperty(...);
device.destroyDevice();
//====== How I do it ==========
// Device would probably be generated via some comptime magic function
const Device = struct {
const Handle = u32;
handle: Handle,
inline pub fn destroyDevice(self: *Self) void {
c.destroyDevice(self.*.handle);
}
inline pub fn destroyDevice(self: *Self) void {
c.destroyDevice(self.*.handle);
}
}
At least on ReleaseFast inspecting with GodBolt it seems to really work as zero-cost though I have doubts it will always be zero cost…
While it is good to be mindful of optimisation so you can write code that is decently efficient by default and can more easily be improved later; this is a case of premature optimisation!
A single wrapper function call is not going to be your bottleneck, and the optimiser would likely inline it for you, you should let it unless you benchmark an improvement.
Also, u32 wrapper shouldn’t be passed by pointer, at least make it const…
Tbh I would simply call the createDevice, destroyDevice, etc… functions directly. There is really no point (IMHO) to create a Device “class” which only stores the device handle just to be able to use the method-call syntax.
But if you still want to go that way, just remove the inline from the methods, the compiler will inline that call anyway in optimized modes, and even if it doesn’t the call overhead will be completely negligible (e.g. a typical case of ‘premature optimization’
PS: that enum(u32) solution suggested by @invlpg and @rpkak is nifty! (but still, all you get out of this wrapper type is the dot-call-syntax - it’s actually a good example why proper UFCS support would be nice to have in Zig, then the dot-call-syntax would also work for ‘standalone functions’ without requiring ‘tricks’ like the enum or struct wrapper)
I like it but unfortunately it won’t work for generic type generator because sometimes I need to wrap C struct but for int types it is really neat
WOOOOW! I didn’t know that I could declare extern functions inside any other type. Though if to think about it file itself is a struct so it is not so surprising but still WOW!
If we talk about wrappers for integers or pointers (especially to those integers and void) it also gives a bit of type safety along the way.
Also when you initially have module name named after library name which exports functions in a format of library_object_method you can write with these wrappers lbrary.object.method(args) instead of library.library_object_method(object, args) which still just a sugar but a very yummy one