!T
is inferredError Union Type
with an error set inferred from our code.anyerror!T
isError Union Type
with global error set, which is is the error set that contains all errors in the entire compilation unit. It is a superset of all other error sets and a subset of none of them.MyError!T
isError Union Type
with user specifiedMyError
error set.
Unfortunately, besides all explicit type coerce (like @intCast :)), error set is the only place zig
will do auto (or hidden) type coerce. The rule is it can be coerced upward (subset to superset), but not downward (superset to subset).
With the inferred error set, sometime this will cause a bit of confusion (at least to me :P). So write down here.
const MyError = error {
WrongInput
};
now let us check some examples
1st example
fn happy1(today_number: usize) !bool {
if (today_number == 0) return MyError.WrongInput;
return today_number != 13;
}
a simple function, what's its inferred error set? It will be MyError
as only MyError.WrongInput
could possibly return there.
fn happy2(today_number: usize) anyerror!bool {
if (today_number == 0) return MyError.WrongInput;
return today_number != 13;
}
what's happy2
's return error set in error union? It is anyerror
, the global one. And it covers MyError
.
const n1 = happy1(5) catch |err| brk: {
switch (err) {
MyError.WrongInput => break :brk false, // mark 1
}
};
const n2 = happy2(5) catch |err| brk: {
switch (err) {
MyError.WrongInput => break :brk false,
else => break :brk false, // mark 2
}
};
so both functions should compile with no problem. Noice that at mark 1
we do not need extra else
as all error types are covered, which is quite different to mark 2
. In face, if we add else
in mark 1
, zig
will complain.
2nd example
The next example will be a bit of tricky.
const n3 = happy1(5) catch |err| brk: {
switch (err) {
anyerror.WrongInput => break :brk false, // mark 1
}
};
const n4 = happy1(5) catch |err| brk: {
switch (err) {
error.WrongInput => break :brk false, // mark 2
}
};
If we run it, zig
will not complain and let it go through. But if we read carefully,
-
mark 1
is usinganyerror.WrongInput
andmark 2
is usingerror.WrongInput
. The former error set is the super set, and the latter one is anonymouserror{WrongInput}
. -
zig
will treat unique names in different error set with same value. So witherr
with error typeMyError
, the match can go through well.
3rd example. bang!
Now change our example a bit again
fn happy3(today_number: usize) !bool {
_ = today_number;
try std.io.getStdOut().writer().print("thinking...", .{});
return false;
}
const n4 = happy3(5) catch |err| brk: {
switch (err) {
MyError.WrongInput => break :brk false, // mark 1
else => break :brk true,
}
};
This will not compile, because zig
inferred from happy3
with print
related error set but without MyError
. So mark 1
will be complained, saying WrongInput
is not a member of error set of err
.
Finally check following example
const MyErrorBase = error{WrongInput};
const MyError = error{
WrongInput2,
} || MyErrorBase; // mark 1
fn happy1(today_number: usize) !bool {
if (today_number == 0) return MyError.WrongInput; // mark 2
return today_number != 13;
}
fn happy4(today_number: usize) MyErrorBase!bool {
return happy1(today_number); // mark 3
}
if compile, we will see following error from zig
es1.zig:27:18: error: expected type 'error{WrongInput}!bool', found '@typeInfo(@typeInfo(@TypeOf(es1.happy1)).Fn.return_type.?).ErrorUnion.error_set!bool'
return happy1(today_number);
~~~~~~^~~~~~~~~~~~~~
es1.zig:27:18: note: 'error.WrongInput2' not a member of destination error set
es1.zig:26:43: note: function return type declared here
fn happy4(today_number: usize) MyErrorBase!bool {
~~~~~~~~~~~^~~~~
the error message complains that happy1
returns a error.WrongInput2
, not in MyErrorBase
. But when we read happy1
, we see nothing about error.WrongInput2
. We have only returned MyError.WrongInput
in once place? what happened??
In face, if we see mark 2
then check mark 1
, we see MyError
is a combined error type of MyErrorBase
and error{WrongInput2}
. So even at mark 2
we only used one value of it, the inferred error set type will be MyError
. And finally when try to coerce it at mark 3
. Because MyError
is super set of MyErrorBase
, no downward rule kicks in. Bang!
summary
Error set is the only thing in zig
will coerce automatically with no downward rule. It is kind of against the principle: no hidden things. But as we all know error handling is such an unenjoyable part of coding. I guess I can understand that why it is designed in this way.
My method to remember this is
- use
!T
as much as possible, especially in logic code. - if something is intended to be reused, use
MyError!T
to helpzig
optimize and identify problems early. -
anyerror!T
is the last resort. Remember if use that we are back to world with hidden things :)
Top comments (2)
The last behavior is interesting, thank you for sharing, it did not match my expectations. If instead of
MyError.WrongInput
you were to writeerror.WrongInput
, everything would work as expected.I don't know what's the ultimately better way of doing things, but I personally always write errors as
error.Foo
and never from their error set definition (egMyErrorSet.Foo
), even when defining the error set, so I've never experienced this situation.Because you write a lot, I will plagiarize you。