#What's the idiomatic way to handle error when using C library?
1 messages · Page 1 of 1 (latest)
Generally implementing around the errors
You can also translate errors into zig errors
^^ mach-glfw is a good example, but to give a more concise example of translating to Zig errors:
fn libErr(ret: c_int) !void {
return switch (ret) {
0 => {},
c.LIB_ERR_FOO => error.Foo,
c.LIB_ERR_BAR => error.Bar,
else => unreachable,
};
}
// then in your code:
try libErr(c.lib_failable_thing());
If you're writing a Zig wrapper for a C library, you'd usually want to make your wrappers around methods call this themselves to convert errors for you. It's also ideal if in each function you mark the possible errors just to make the error set correct (to make exhaustive handling easier):
fn failableThing() !void {
libErr(c.lib_failable_thing()) catch |err| switch (err) {
return @errSetCast(error{ Foo, Bar }, err); // this function can only return these two errors!
};
}
...but that can be quite a lot of boilerplate, so it's fair to not bother (especially since an annoying amount of C libraries don't actually define what errors a function can return!). Note that if you do do that, you can inline a bit of the boilerplate into the error-translation function:
fn libErr(ret: c_int, comptime Errs: type) Errs!void {
const err = switch (ret) {
0 => return,
c.LIB_ERR_FOO => error.Foo,
c.LIB_ERR_BAR => error.Bar,
c.LIB_ERR_BAZ => error.Baz,
else => unreachable,
};
return @errSetCast(Errs, err);
}
fn failableThing() !void {
try libErr(c.lib_failable_thing(), error{ Foo, Bar });
}
do you need @errSetCast there? I'd usually avoid that, because it can generate a safety check
if the error set is a subset of the destination set, it's unnecessary
no, the point is that we're going the other way
we want a safety check there
because we're saying "translate the error, but then assume only this subset is possible"
if we hit an error that should be impossible, there's been a programmer error of some sort (either in the lib or its documentation, or in the wrapper)
you already get that safety check from the unreachable branch - meaning all that happens here is you get two redundant safety checks in safe modes, and still UB in unsafe modes
type of err is already error{Foo, Bar, Baz}
Yes, and we're trying to reduce it further
aight, maybe I'm just not seeing the broader picture
Foo, Bar, Baz is all the possible errors the library defines
And the function refines it to the errors a single function can return
no worries
A lot of C library would return error code, which is not error type but integer.
With C or with Zig?
With C I would use a per-file enum of the possible errors + Zig cli helper to validate the global error set by combining the local ones.
That way you dont forget stuff in the global error set.
Ofc if you want to avoid the overhead of the safety check you can just @setRuntimeSafety
With C you would need a struct for every function to make errors or enums local to the function.