#What's the idiomatic way to handle error when using C library?

1 messages · Page 1 of 1 (latest)

ivory marlin
#

It's more like a opinion about coding style but I wonder if there's an elegant way to handle it in Zig. A lot of C library would return error code, which is not error type but integer.

rose root
#

Generally implementing around the errors

#

You can also translate errors into zig errors

keen yoke
#

^^ 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 });
}
lost wagon
#

if the error set is a subset of the destination set, it's unnecessary

keen yoke
#

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)

lost wagon
#

type of err is already error{Foo, Bar, Baz}

keen yoke
#

Yes, and we're trying to reduce it further

lost wagon
#

aight, maybe I'm just not seeing the broader picture

keen yoke
#

Foo, Bar, Baz is all the possible errors the library defines

#

And the function refines it to the errors a single function can return

lost wagon
#

ah, I see

#

right, make sense

#

pardon for the confusion

keen yoke
#

no worries

cunning shoal
#

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.

keen yoke
#

Ofc if you want to avoid the overhead of the safety check you can just @setRuntimeSafety

cunning shoal
#

With C you would need a struct for every function to make errors or enums local to the function.