# Errors

## Error Set Type

An error set is like an enum. However, each error name across the entire compilation gets assigned an unsigned integer greater than 0. You are allowed to declare the same error name more than once, and if you do, it gets assigned the same integer value.

The number of unique error values across the entire compilation should determine the size of the error set type. However right now it is hard coded to be a u16. See #768.

You can coerce an error from a subset to a superset:

test.zig

const std =@import("std");constFileOpenError= error {AccessDenied,OutOfMemory,FileNotFound,};constAllocationError= error {OutOfMemory,};test "coerce subset to superset"{const err = foo(AllocationError.OutOfMemory);    std.debug.assert(err ==FileOpenError.OutOfMemory);}fn foo(err:AllocationError)FileOpenError{return err;}
$zig test test.zig1/1 test "coerce subset to superset"...OKAll1 tests passed. But you cannot coerce an error from a superset to a subset: test.zig constFileOpenError= error {AccessDenied,OutOfMemory,FileNotFound,};constAllocationError= error {OutOfMemory,};test "coerce superset to subset"{ foo(FileOpenError.OutOfMemory)catch{};}fn foo(err:FileOpenError)AllocationError{return err;} $ zig test test.zig./docgen_tmp/test.zig:16:12: error: expected type 'AllocationError', found 'FileOpenError'return err;^./docgen_tmp/test.zig:2:5: note:'error.AccessDenied'not a member of destination error setAccessDenied,^./docgen_tmp/test.zig:4:5: note:'error.FileNotFound'not a member of destination error setFileNotFound,^

There is a shortcut for declaring an error set with only 1 value, and then getting that value:

const err = error.FileNotFound;

This is equivalent to:

const err =(error {FileNotFound}).FileNotFound;

This becomes useful when using Inferred Error Sets.

### The Global Error Set

anyerror refers to the global error set. This 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.

You can coerce any error set to the global one, and you can explicitly cast an error of the global error set to a non-global one. This inserts a language-level assert to make sure the error value is in fact in the destination error set.

The global error set should generally be avoided because it prevents the compiler from knowing what errors are possible at compile-time. Knowing the error set at compile-time is better for generated documentation and helpful error messages, such as forgetting a possible error value in a switch.

## Error Union Type

An error set type and normal type can be combined with the ! binary operator to form an error union type. You are likely to use an error union type more often than an error set type by itself.

Here is a function to parse a string into a 64-bit integer:

test.zig

const std =@import("std");const maxInt = std.math.maxInt;pub fn parseU64(buf:[]const u8, radix: u8)!u64 {var x: u64 =0;for(buf)|c|{const digit = charToDigit(c);if(digit >= radix){return error.InvalidChar;}// x *= radixif(@mulWithOverflow(u64, x, radix,&x)){return error.Overflow;}// x += digitif(@addWithOverflow(u64, x, digit,&x)){return error.Overflow;}}return x;}fn charToDigit(c: u8) u8 {returnswitch(c){'0'...'9'=> c -'0','A'...'Z'=> c -'A'+10,'a'...'z'=> c -'a'+10,else=> maxInt(u8),};}test "parse u64"{const result =try parseU64("1234",10);    std.debug.assert(result ==1234);}

### Merging Error Sets

Use the || operator to merge two error sets together. The resulting error set contains the errors of both error sets. Doc comments from the left-hand side override doc comments from the right-hand side. In this example, the doc comments for C.PathNotFound is A doc comment.

This is especially useful for functions which return different error sets depending on comptime branches. For example, the Zig standard library uses LinuxFileOpenError || WindowsFileOpenError for the error set of opening files.

test.zig

const A = error{NotDir,/// A doc commentPathNotFound,};const B = error{OutOfMemory,/// B doc commentPathNotFound,};const C = A || B;fn foo() C!void{return error.NotDir;}test "merge error sets"{if(foo()){@panic("unexpected");}else|err|switch(err){        error.OutOfMemory=>@panic("unexpected"),        error.PathNotFound=>@panic("unexpected"),        error.NotDir=>{},}}
$zig test test.zig1/1 test "merge error sets"...OKAll1 tests passed. ### Inferred Error Sets Because many functions in Zig return a possible error, Zig supports inferring the error set. To infer the error set for a function, use this syntax: test.zig // With an inferred error setpub fn add_inferred(comptime T: type, a: T, b: T)!T {var answer: T =undefined;returnif(@addWithOverflow(T, a, b,&answer)) error.Overflowelse answer;}// With an explicit error setpub fn add_explicit(comptime T: type, a: T, b: T)Error!T {var answer: T =undefined;returnif(@addWithOverflow(T, a, b,&answer)) error.Overflowelse answer;}constError= error {Overflow,};const std =@import("std");test "inferred error set"{if(add_inferred(u8,255,1))|_| unreachable else|err|switch(err){ error.Overflow=>{},// ok}} $ zig test test.zig1/1 test "inferred error set"...OKAll1 tests passed.

When a function has an inferred error set, that function becomes generic and thus it becomes trickier to do certain things with it, such as obtain a function pointer, or have an error set that is consistent across different build targets. Additionally, inferred error sets are incompatible with recursion.

In these situations, it is recommended to use an explicit error set. You can generally start with an empty error set and let compile errors guide you toward completing the set.

These limitations may be overcome in a future version of Zig.

## Error Return Traces

Error Return Traces show all the points in the code that an error was returned to the calling function. This makes it practical to use try everywhere and then still be able to know what happened if an error ends up bubbling all the way out of your application.

test.zig

pub fn main()!void{try foo(12);}fn foo(x: i32)!void{if(x >=5){try bar();}else{try bang2();}}fn bar()!void{if(baz()){try quux();}else|err|switch(err){        error.FileNotFound=>try hello(),else=>try another(),}}fn baz()!void{try bang1();}fn quux()!void{try bang2();}fn hello()!void{try bang2();}fn another()!void{try bang1();}fn bang1()!void{return error.FileNotFound;}fn bang2()!void{return error.PermissionDenied;}
$zig build-exe test.zig$ ./testerror:PermissionDenied/deps/zig/docgen_tmp/test.zig:39:5:0x22ef02in bang1 (test)return error.FileNotFound;^/deps/zig/docgen_tmp/test.zig:23:5:0x22eddfin baz (test)try bang1();^/deps/zig/docgen_tmp/test.zig:43:5:0x22eda2in bang2 (test)return error.PermissionDenied;^/deps/zig/docgen_tmp/test.zig:31:5:0x22eecfin hello (test)try bang2();^/deps/zig/docgen_tmp/test.zig:17:31:0x22ed6ein bar (test)        error.FileNotFound=>try hello(),^/deps/zig/docgen_tmp/test.zig:7:9:0x22ec5cin foo (test)try bar();^/deps/zig/docgen_tmp/test.zig:2:5:0x22a9e4in main (test)try foo(12);^

Look closely at this example. This is no stack trace.

You can see that the final error bubbled up was PermissionDenied, but the original error that started this whole thing was FileNotFound. In the bar function, the code handles the original error code, and then returns another one, from the switch statement. Error Return Traces make this clear, whereas a stack trace would look like this:

test.zig

pub fn main()void{    foo(12);}fn foo(x: i32)void{if(x >=5){        bar();}else{        bang2();}}fn bar()void{if(baz()){        quux();}else{        hello();}}fn baz()bool{return bang1();}fn quux()void{    bang2();}fn hello()void{    bang2();}fn bang1()bool{returnfalse;}fn bang2()void{@panic("PermissionDenied");}
$zig build-exe test.zig$ ./testPermissionDenied/deps/zig/docgen_tmp/test.zig:38:5:0x2302d6in bang2 (test)@panic("PermissionDenied");^/deps/zig/docgen_tmp/test.zig:30:10:0x230a38in hello (test)    bang2();^/deps/zig/docgen_tmp/test.zig:17:14:0x2302bain bar (test)        hello();^/deps/zig/docgen_tmp/test.zig:7:12:0x22e955in foo (test)        bar();^/deps/zig/docgen_tmp/test.zig:2:8:0x22a7edin main (test)    foo(12);^/deps/zig/lib/std/start.zig:243:22:0x2046efin std.start.posixCallMainAndExit (test)            root.main();^/deps/zig/lib/std/start.zig:123:5:0x2044cfin std.start._start (test)@call(.{.modifier =.never_inline }, posixCallMainAndExit,.{});^(process terminated by signal)

Here, the stack trace does not explain how the control flow in bar got to the hello() call. One would have to open a debugger or further instrument the application in order to find out. The error return trace, on the other hand, shows exactly how the error bubbled up.

This debugging feature makes it easier to iterate quickly on code that robustly handles all error conditions. This means that Zig developers will naturally find themselves writing correct, robust code in order to increase their development pace.

Error Return Traces are enabled by default in Debug and ReleaseSafe builds and disabled by default in ReleaseFast and ReleaseSmall builds.

There are a few ways to activate this error return tracing feature:

• Return an error from main
• An error makes its way to catch unreachable and you have not overridden the default panic handler
• Use errorReturnTrace to access the current return trace. You can use std.debug.dumpStackTrace to print it. This function returns comptime-known null when building without error return tracing support.

### Implementation Details

To analyze performance cost, there are two cases:

• when no errors are returned
• when returning errors

For the case when no errors are returned, the cost is a single memory write operation, only in the first non-failable function in the call graph that calls a failable function, i.e. when a function returning void calls a function returning error. This is to initialize this struct in the stack memory:

pub constStackTrace=struct{    index: usize,    instruction_addresses:[N]usize,};

Here, N is the maximum function call depth as determined by call graph analysis. Recursion is ignored and counts for 2.

A pointer to StackTrace is passed as a secret parameter to every function that can return an error, but it's always the first parameter, so it can likely sit in a register and stay there.

That's it for the path when no errors occur. It's practically free in terms of performance.

When generating the code for a function that returns an error, just before the return statement (only for the return statements that return errors), Zig generates a call to this function:

// marked as "no-inline" in LLVM IRfn __zig_return_error(stack_trace:*StackTrace)void{    stack_trace.instruction_addresses[stack_trace.index]=@returnAddress();    stack_trace.index =(stack_trace.index +1)% N;}

The cost is 2 math operations plus some memory reads and writes. The memory accessed is constrained and should remain cached for the duration of the error return bubbling.

As for code size cost, 1 function call before a return statement is no big deal. Even so, I have a plan to make the call to __zig_return_error a tail call, which brings the code size cost down to actually zero. What is a return statement in code without error return tracing can become a jump instruction in code with error return tracing.