Zig NEWS

LI Yu
LI Yu

Posted on

`!T`, `anyerror!T` and `MyError!T`

  • !T is inferred Error Union Type with an error set inferred from our code.

  • anyerror!T is Error 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 is Error Union Type with user specified MyError 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
};
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
        }
    };
Enter fullscreen mode Exit fullscreen mode

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
        }
    };
Enter fullscreen mode Exit fullscreen mode

If we run it, zig will not complain and let it go through. But if we read carefully,

  1. mark 1 is using anyerror.WrongInput and mark 2 is using error.WrongInput. The former error set is the super set, and the latter one is anonymous error{WrongInput}.
  2. zig will treat unique names in different error set with same value. So with err with error type MyError, 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,
    }
};
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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 {
                               ~~~~~~~~~~~^~~~~
Enter fullscreen mode Exit fullscreen mode

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

  1. use !T as much as possible, especially in logic code.
  2. if something is intended to be reused, use MyError!T to help zig optimize and identify problems early.
  3. anyerror!T is the last resort. Remember if use that we are back to world with hidden things :)

Top comments (2)

Collapse
 
kristoff profile image
Loris Cro

The last behavior is interesting, thank you for sharing, it did not match my expectations. If instead of MyError.WrongInput you were to write error.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 (eg MyErrorSet.Foo), even when defining the error set, so I've never experienced this situation.

Collapse
 
htqx profile image
thanks you see

Because you write a lot, I will plagiarize you。