-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Description
Error values in Zig can be constructed in three ways:
@errorFromInt. This is just a low-level primitive which is useful in niche scenarios (usually ABI stuff, such as passing an error as user data through a C API).error.Foo. This is by far the most common way.ErrorSet.Foo, whereErrorSetis an error set type. This is used incredibly rarely.
It's clear that ErrorSet.Foo is theoretically a redundant form, because it's exactly identical to @as(ErrorSet, error.Foo). The type annotation in that alternative is only relevant in very niche scenarios; in virtually all cases, you can get away with just writing error.Foo instead. In practice, people almost exclusively write error.Foo instead of ErrorSet.Foo; to the point where I encounter the latter so rarely it briefly confuses me whenever I do. As is often the case, having two ways to do the same thing hinders readability.
In principle, ErrorSet.Foo could have the benefit of resistance against typos, because accidentally writing ErrorSet.Fooo is a compile error, while error.Fooo is not. However, I don't find this argument compelling. There are two common scenarios where writing error values: in switch items (by which I mean the values to the left of =>), and in return <error value>. In both of these cases, there is already a specific error type which is expected. In the latter case, the function could have an inferred error set (i.e. !T instead of ErrorSet!T), but if a user is going to go to the effort of writing return ErrorSet.Foo everywhere, it's more valuable to redirect that effort to the return type, where all return values are guaranteed to be checked, the code becomes more readable, and generated documentation is improved.
Lastly, here are two more annoying consequences of ErrorSet.Foo:
- They introduce a special case to Decl Literals. Usually,
.Foois equivalent toResultType.Foo, but this is not the case for error sets (you can't write.MyErroras shorthand forResultErrorSet.MyError). This shows up as an explicit special case in the compiler implementation. - They allow constructing error values at comptime with
@field(for instance,@field(anyerror, my_string)). This isn't used in practice, but could hugely harm readability of a codebase if it was (e.g. it could become very difficult to find where an error was returned from).
As a result of the above, I propose disallowing ErrorSet.Foo (including anyerror.Foo) in favour of error.Foo. This would also disallow @field(ErrorSet, "foo") (including @field(anyerror, "foo")), so it becomes impossible to construct error values at comptime from a string error name.