I have a strange need for function that returns Cancelable!T, where T could also be an error union. Even though the compiler recognizes the nested error union, I can’t return the T value.
src/select.zig:203:29: error: expected type 'error{Canceled}!error{Foo}!i32', found 'error{Foo}!i32'
return fut.getResult();
~~~~~~~~~~~~~^~
src/select.zig:203:29: note: error union payload 'i32' cannot cast into error union payload 'error{Foo}!i32'
src/select.zig:186:54: note: function return type declared here
pub fn wait(rt: *Runtime, future: anytype) Cancelable!FutureResult(@TypeOf(future)) {
~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
How can I return value of type error{Foo}!i32 from function that has return type error{Canceled}!error{Foo}!i32?
Or do I need to wrap it in a struct, e.g. Cancelable!struct { value: T }?
If I ever had such a need, I would probably extract the error from T, make a merged error union Cancelable || ErrorFromT and use that in the return value.
2 Likes
Here, I was kind of interested how that would turn out, so I made a little naive sketch: https://codeberg.org/spiffyk/zig-test/src/branch/main/decorated-error.zig
3 Likes
Yes, that’s what I was using until now, but now I found the need to know on which layer the error happened.
Errors are not exactly values in Zig, so you cannot return nested errors like that. Errors are a dedicated channel and not a value you can handle directly in this way.
Like @spiffyk mentioned, the typical pattern is to have several named error unions that you can combine into the error union for that specific function. It would be up to the caller to determine which of the error variants it wants to handle.
Without knowing more about your library it’s hard to suggest a good alternative. @spiffyk has a good comptime option.
If you want to allow the user to define it, you could potentially @import("root"); to get the module root and let them pub const RuntimeError = error{...}; which you then add to your union:
/// In Module root (i.e. consumer of library)
pub const RuntimeError = error{ Foo };
/// In your library
const root = @import("root");
const ClientError = if (@hasDecl(root, "RuntimeError") root.RuntimeError else error{};
const SelectError = error {Cancelable} || ClientError;
I don’t have a problem solving it, I wrapped the payload a struct and that’s fine.
The pattern is:
const result = try task.wait(); // the wait can be canceled
const value = try result.value; // the result of the waiting could also have error set
I need to handle these errors differently, but due to how errors work in Zig, result.value could also contain error.Canceled and I want to distinguish that.
My question mainly about the fact that the compiler recognizes error{Canceled}!error{Foo}!i32 as a type, yet it does not allow me to return error{Foo}!i32 from the function.
It seems like a compiler bug. Either the nested error union should not be allowed to exist, or I should be allowed to return the inner error union.
2 Likes
It seems like a compiler bug. Either the nested error union should not be allowed to exist, or I should be allowed to return the inner error union.
Based.
I need to handle these errors differently, but due to how errors work in Zig, result.value could also contain error.Canceled and I want to distinguish that.
Just switch on the error set. You can do it two ways:
errdefer |e| switch(e){};
try erroringFunction();
erroringFunction() catch |e| switch(e){};
This way, you could handle error.Cancelled and error.Foo separately, while still having them both be part of the error set that the function returns. (Or even having the function return !i32).
1 Like
Imagine this type error{Canceled}!error{Foo,Canceled}!u32. If I merge them, I get error{Foo,Canceled}!u32. How do I know which one triggered it?
1 Like
You rename one of them before merging error unions.
switch (err) {
error.Canceled => return error.InnerCanceled,
else => |e| return e,
}
1 Like
You can also do:
const InnerError = error{Foo};
// Somewhere later
switch (err) {
InnerError.Foo => return err,
error.Canceled => return e,
}
Or just use more specific errors in general: XxxCanceled and YyyCanceled.
1 Like
I understand your point. After some experimentation, I believe the error{Canceled}!error{Foo}!i32 type shouldn’t exist. Your approach of wrapping it with a structure is correct.
1 Like
errdefer |e| is going away though.
2 Likes