From f0697c28f80d64c544302aea576e41ebc443b41c Mon Sep 17 00:00:00 2001
From: Andrew Kelley Use If you want to provide a default value, you can use the undefined
to leave variables uninitialized:catch
binary operator:catch
operator must
match the unwrapped error union type, or be of type noreturn
.
Let's say you wanted to return the error if you got one, otherwise continue with the function logic:
{#code_begin|syntax#} @@ -3033,6 +3038,7 @@ fn doAThing(str: []u8) !void { from the current function with the same error. Otherwise, the expression results in the unwrapped value. + {#header_close#}Maybe you know with complete certainty that an expression will never be an error. In this case you can do this: @@ -3047,7 +3053,7 @@ fn doAThing(str: []u8) !void {
Finally, you may want to take a different action for every situation. For that, we combine
- the if
and switch
expression:
+ the {#link|if#} and {#link|switch#} expression:
The other component to error handling is defer statements.
- In addition to an unconditional defer
, Zig has errdefer
,
+ In addition to an unconditional {#link|defer#}, Zig has errdefer
,
which evaluates the deferred expression on block exit path if and only if
the function returned with an error from the block.
A couple of other tidbits about error handling:
@@ -3223,7 +3231,174 @@ test "inferred error set" { {#header_close#} {#header_close#} {#header_open|Error Return Traces#} -TODO
++ 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 {#link|try#} everywhere and then still be able to know what happened if an error ends up bubbling all the way out of your application. +
+ {#code_begin|exe_err#} +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; +} + {#code_end#} ++ 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:
+
+ 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 {#link|Debug#} and {#link|ReleaseSafe#} builds and disabled by default in {#link|ReleaseFast#} and {#link|ReleaseSmall#} builds. +
++ There are a few ways to activate this error return tracing feature: +
+catch unreachable
and you have not overridden the default panic handlerstd.debug.dumpStackTrace
to print it. This function returns comptime-known {#link|null#} when building without error return tracing support.+ To analyze performance cost, there are two cases: +
+
+ 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:
+
+ 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:
+
+ 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.
+
+ Just like {#link|undefined#}, null
has its own type, and the only way to use it is to
+ cast it to a different type:
+
- Zig has three build modes: + Zig has four build modes:
To add standard build options to a build.zig
file:
@@ -5448,14 +5633,16 @@ pub fn build(b: &Builder) void {
This causes these options to be available:
- -Drelease-safe=(bool) optimizations on and safety on
- -Drelease-fast=(bool) optimizations on and safety off
+ -Drelease-safe=[bool] optimizations on and safety on
+ -Drelease-fast=[bool] optimizations on and safety off
+ -Drelease-small=[bool] size optimizations on and safety off
{#header_open|Debug#}
$ zig build-exe example.zig
$ zig build-exe example.zig --release-small
+
@@ -5482,7 +5680,7 @@ pub fn build(b: &Builder) void {
detected at compile-time, Zig emits an error. Most undefined behavior that
cannot be detected at compile-time can be detected at runtime. In these cases,
Zig has safety checks. Safety checks can be disabled on a per-block basis
- with @setRuntimeSafety
. The {#link|ReleaseFast#}
+ with {#link|setRuntimeSafety#}. The {#link|ReleaseFast#}
build mode disables all safety checks in order to facilitate optimizations.