I have been working on a zig library and a bulk of it is interfacing with a Json Rpc server. And i have been thinking on what would be the best way to handle the error responses. One way that I was thinking was to use
@panic after parsing the response with the error message. The other option would be to return a union of either the result or the error as the value and let the user handle how they see fit.
On one hand in most cases the error result would essentially be a unrecoverable error and the use of panic might be justified but on the other hand returning the union would give the user more options in case they still want the execution to continue.
What approach would you recommend or other approaches would you take?
If you call
@panic yourself, your caller has no choice. If you return an
error, you give your caller the choice to call
@panic themselves, or do something else. I personally prefer returning an error.
And what if you wanted to return a more explicit error thats is based on the rpc response instead of something generic like RpcErrorResponse?
Welcome to the forum, @Raiden
If you can think of ways that a user could reasonably recover from the error, then an error union is often the way to go.
I’m working on a project right now where memory usage on devices can get beyond gigabytes quite easily. However, the total memory requested should have been considered up front (because in this case, it can be calculated quite easily). I’m going with panic because if the request was unreasonable, then the request needs to get rethought in the first place. So in this case, I’m using panics to enforce how the app should approached in the first place.
Likewise, consider panics on out of bounds slice indices for debug. In Zig, we can check that stuff in debug quite easily and then compile out the panics with higher optimization levels. In other words, it’s not quite either-or. You can have optional panics depending on if you’re in debug or not.
I think a mixed approach is usually preferable.
A while back I explored customerrors a little bit in this topic Custom errors with payloads (with current zig), however I don’t think it mixes well with normal zig code.
I think the current best way, is to do what @andrewrk suggested here.
Because it can simply be used with zigs error unions.
If you are interested to get more information about the error, the user at the call site uses a config parameter to pass a pointer to a struct you place at the call site, this struct then gets filled by the called function.
This would also allow you to use this extra information to write a wrapper function that panics with that information, in cases where you don’t want to try to handle the error.
Maybe you could even pack both into one function if you calculate the return type based on the config and use clever helper inline functions, but I think that might over complicate things. Just writing the wrapper that panics when you need one seems simpler.
The cover of “The Hitchhiker’s Guide to the Galaxy” can help. It reads:
It does not apply always on API design, but it’s good rule of thumb.
Before you panic I’d think you’d want to really be really sure that the state is not only unrecoverable but also not ignorable. Think about the consequences of a panic. A panic means that any application thread that depends on your library will crash in that state. A single threaded process will crash the entire program. Is that something your users will be ok with?
If the error is a result of your library being abused (e.g API functions called out of order) then I think a panic is warranted to force the user to use your library correctly.
Thank you all for the replies. All suggested patterns are very good and everyone has some very nice mental models on how to approach this.
I will most likely go with the mix approach but with something similar as @Sze suggested that can essencially track those error messages in case they happen.
I also think that the suggestion that @AndrewCodeDev made by having different types on actions depending on the build is something that I will use.
For production code, I tend to do the following;
- panic when the error should not happen (but we programmed defensively) because in this case we are in unchartered territory, there is really nothing one could do sensibly,
- proper error result when the error is something that could be expected (such as an Internet server timeouting, or sending error responses). IMHO, using a library should not crash the program which use it, when the problem is something that should be expected.
For experimental code, panic is a quick and easy way to signal a problem.