C-compatibilityFrom C, it is possible to use the generated error structs. Some additional macros can be written to ensure even smoother usage.
Calling C, it can be useful to have a seamless conversion of result-code based calls (1), errno-calls (2) and calls with error as in-parameter (3) e.g:
// 1
API_RESULT do_something(int a, Foo *foo);
// 2
bool success = do_something(int a, Foo *foo);
if (!success) { /* do something with errno */ }
// 3
Error err;
bool success = do_something(int a, Foo *foo, &err);
if (!success) { /* do something with err */ }
All of these could be automatically wrapped by C2 later on. However, it's not important for the first version.
Function signaturesZig uses ! to denote errors or error separator, e.g:
pub fn parseU64(buf: []const u8, radix: u8) !u64 { ... } // inferred error
pub fn parseU64(buf: []const u8, radix: u8) anyerror!u64 { ... } // catchall
pub fn parseU64(buf: []const u8, radix: u8) ErrorSetX!u64 { ... } // throws errors from the ErrorSetX enum.
For Go it's just part of the signature.
For C2 there are a bunch of solutions:
func u64 parseU64(u8* buf, u8 radix) !anyerror { ... }
func u64 parseU64(u8* buf, u8 radix) anyerror!! { ... }
func u64 ! anyerror parseU64(u8* buf, u8 radix) { ... }
func u64 !! anyerror parseU64(u8* buf, u8 radix) { ... }
func u64 parseU64(u8* buf, u8 radix) fails(anyerror) { ... }
func u64 parseU64(u8* buf, u8 radix) throws anyerror { ... }
func u64, anyerror parseU64(u8* buf, u8 radix) { ... }
func u64, anyerror!! parseU64(u8* buf, u8 radix) { ... }
func u64 parseU64(u8* buf, u8 radix) anyerror!! { ... }
PayloadI believe the idea of Zig to simply make the error code an enum union is good. Unlike other schemes, this means that adding additional error codes does not break the signature. The enum union should be register sized but at least 32 bit, meaning 64 or 32 bits in practice.
Encoding additional information some further discussion, there are essentially 3 approaches:
- No additional data, just the enum
- Error is encoded in the result
- A thread local errno can encode information
(3) breaks purity of functions.
(2) changes size in return object if the error payload is allowed to vary. If (2) returns a pointer then cleanup of that pointer might be hard to enforce.
(1) Is limiting, and will probably mean ad-hoc solutions passing in &err-structs.
I'm slightly partial to (2) despite the drawbacks.