It entirely depends on why you might need extra data.
For instance, if you want to know the location where something which uses an index fails, you can use a cursor pattern:
var cursor: usize = 0;
const result = something.spanAnArray(&cursor) catch |err| {
log.error("Error {s} at index {d}", .{@errorName(err), cursor});
return err;
};
Here, the assumption is that cursor.* is used to index and advance along some array.
If you really need a tagged error union, you can simply use a tagged union, and use ordinary control flow to handle it. These can be promoted to more ordinary errors when desired, so as to be compatible with the expected idioms of Zig.
This doesn’t actually get around the problem of error payloads, because unwrapOrErr requires that E be an error.
One could do something quite similar, where E is a struct which includes an anyerror field, or a field containing the relevant error set, then the rest of the struct could carry the payload.
This is true but you get to convert the switch into a poor man’s match statement, which looks cool, but yeah you can’t attach anything to it unfortunately.
Quite a few people in the Zig community have rallied around the “Diagnostics pattern” where you return and handle errors per usual, but also allow the caller to interrogate a Diagnostics instance when an error happens. This contains useful information about the error, such as col/line for a parser error, or maybe an errorstring/code combination for a file error.
Depending on situation, the Diagnostics instance can be passed in to every relevant fallible function, or be part of initialization for session-oriented situations.
The last one is kinda interesting in that it uses InKryption’s idea of using pub const Diagnostic = union(std.meta.FieldEnum(MyErrorType)) to make sure error enums and diagnostics are always kept in sync. If you forget to sync up, you get a compile error. Very nice imo!
You might be interested in this thread, and my response, which in short was “error codes are not values, errors are a refusal to return a value”. This is my current understanding of errors. The current default way to return values as “erroneous” is to use a tagged union.