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.