Skip to content

Commit f0697c2

Browse files
committedJun 14, 2018
langref: docs for error return traces
See #367
·
0.15.20.3.0
1 parent cdf1e36 commit f0697c2

File tree

1 file changed

+206
-8
lines changed

1 file changed

+206
-8
lines changed
 

‎doc/langref.html.in‎

Lines changed: 206 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ test "initialization" {
590590
x = 1;
591591
}
592592
{#code_end#}
593+
{#header_open|undefined#}
593594
<p>Use <code>undefined</code> to leave variables uninitialized:</p>
594595
{#code_begin|test#}
595596
const assert = @import("std").debug.assert;
@@ -602,6 +603,7 @@ test "init with undefined" {
602603
{#code_end#}
603604
{#header_close#}
604605
{#header_close#}
606+
{#header_close#}
605607
{#header_open|Integers#}
606608
{#header_open|Integer Literals#}
607609
{#code_begin|syntax#}
@@ -2999,6 +3001,7 @@ test "parse u64" {
29993001
<li>You know with complete certainty it will not return an error, so want to unconditionally unwrap it.</li>
30003002
<li>You want to take a different action for each possible error.</li>
30013003
</ul>
3004+
{#header_open|catch#}
30023005
<p>If you want to provide a default value, you can use the <code>catch</code> binary operator:</p>
30033006
{#code_begin|syntax#}
30043007
fn doAThing(str: []u8) void {
@@ -3011,6 +3014,8 @@ fn doAThing(str: []u8) void {
30113014
a default value of 13. The type of the right hand side of the binary <code>catch</code> operator must
30123015
match the unwrapped error union type, or be of type <code>noreturn</code>.
30133016
</p>
3017+
{#header_close#}
3018+
{#header_open|try#}
30143019
<p>Let's say you wanted to return the error if you got one, otherwise continue with the
30153020
function logic:</p>
30163021
{#code_begin|syntax#}
@@ -3033,6 +3038,7 @@ fn doAThing(str: []u8) !void {
30333038
from the current function with the same error. Otherwise, the expression results in
30343039
the unwrapped value.
30353040
</p>
3041+
{#header_close#}
30363042
<p>
30373043
Maybe you know with complete certainty that an expression will never be an error.
30383044
In this case you can do this:
@@ -3047,7 +3053,7 @@ fn doAThing(str: []u8) !void {
30473053
</p>
30483054
<p>
30493055
Finally, you may want to take a different action for every situation. For that, we combine
3050-
the <code>if</code> and <code>switch</code> expression:
3056+
the {#link|if#} and {#link|switch#} expression:
30513057
</p>
30523058
{#code_begin|syntax#}
30533059
fn doAThing(str: []u8) void {
@@ -3062,9 +3068,10 @@ fn doAThing(str: []u8) void {
30623068
}
30633069
}
30643070
{#code_end#}
3071+
{#header_open|errdefer#}
30653072
<p>
30663073
The other component to error handling is defer statements.
3067-
In addition to an unconditional <code>defer</code>, Zig has <code>errdefer</code>,
3074+
In addition to an unconditional {#link|defer#}, Zig has <code>errdefer</code>,
30683075
which evaluates the deferred expression on block exit path if and only if
30693076
the function returned with an error from the block.
30703077
</p>
@@ -3095,6 +3102,7 @@ fn createFoo(param: i32) !Foo {
30953102
the verbosity and cognitive overhead of trying to make sure every exit path
30963103
is covered. The deallocation code is always directly following the allocation code.
30973104
</p>
3105+
{#header_close#}
30983106
<p>
30993107
A couple of other tidbits about error handling:
31003108
</p>
@@ -3223,7 +3231,174 @@ test "inferred error set" {
32233231
{#header_close#}
32243232
{#header_close#}
32253233
{#header_open|Error Return Traces#}
3226-
<p>TODO</p>
3234+
<p>
3235+
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.
3236+
</p>
3237+
{#code_begin|exe_err#}
3238+
pub fn main() !void {
3239+
try foo(12);
3240+
}
3241+
3242+
fn foo(x: i32) !void {
3243+
if (x >= 5) {
3244+
try bar();
3245+
} else {
3246+
try bang2();
3247+
}
3248+
}
3249+
3250+
fn bar() !void {
3251+
if (baz()) {
3252+
try quux();
3253+
} else |err| switch (err) {
3254+
error.FileNotFound => try hello(),
3255+
else => try another(),
3256+
}
3257+
}
3258+
3259+
fn baz() !void {
3260+
try bang1();
3261+
}
3262+
3263+
fn quux() !void {
3264+
try bang2();
3265+
}
3266+
3267+
fn hello() !void {
3268+
try bang2();
3269+
}
3270+
3271+
fn another() !void {
3272+
try bang1();
3273+
}
3274+
3275+
fn bang1() !void {
3276+
return error.FileNotFound;
3277+
}
3278+
3279+
fn bang2() !void {
3280+
return error.PermissionDenied;
3281+
}
3282+
{#code_end#}
3283+
<p>
3284+
Look closely at this example. This is no stack trace.
3285+
</p>
3286+
<p>
3287+
You can see that the final error bubbled up was <code>PermissionDenied</code>,
3288+
but the original error that started this whole thing was <code>FileNotFound</code>. In the <code>bar</code> function, the code handles the original error code,
3289+
and then returns another one, from the switch statement. Error Return Traces make this clear, whereas a stack trace would look like this:
3290+
</p>
3291+
{#code_begin|exe_err#}
3292+
pub fn main() void {
3293+
foo(12);
3294+
}
3295+
3296+
fn foo(x: i32) void {
3297+
if (x >= 5) {
3298+
bar();
3299+
} else {
3300+
bang2();
3301+
}
3302+
}
3303+
3304+
fn bar() void {
3305+
if (baz()) {
3306+
quux();
3307+
} else {
3308+
hello();
3309+
}
3310+
}
3311+
3312+
fn baz() bool {
3313+
return bang1();
3314+
}
3315+
3316+
fn quux() void {
3317+
bang2();
3318+
}
3319+
3320+
fn hello() void {
3321+
bang2();
3322+
}
3323+
3324+
fn bang1() bool {
3325+
return false;
3326+
}
3327+
3328+
fn bang2() void {
3329+
@panic("PermissionDenied");
3330+
}
3331+
{#code_end#}
3332+
<p>
3333+
Here, the stack trace does not explain how the control
3334+
flow in <code>bar</code> got to the <code>hello()</code> call.
3335+
One would have to open a debugger or further instrument the application
3336+
in order to find out. The error return trace, on the other hand,
3337+
shows exactly how the error bubbled up.
3338+
</p>
3339+
<p>
3340+
This debugging feature makes it easier to iterate quickly on code that
3341+
robustly handles all error conditions. This means that Zig developers
3342+
will naturally find themselves writing correct, robust code in order
3343+
to increase their development pace.
3344+
</p>
3345+
<p>
3346+
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.
3347+
</p>
3348+
<p>
3349+
There are a few ways to activate this error return tracing feature:
3350+
</p>
3351+
<ul>
3352+
<li>Return an error from main</li>
3353+
<li>An error makes its way to <code>catch unreachable</code> and you have not overridden the default panic handler</li>
3354+
<li>Use {#link|errorReturnTrace#} to access the current return trace. You can use <code>std.debug.dumpStackTrace</code> to print it. This function returns comptime-known {#link|null#} when building without error return tracing support.</li>
3355+
</ul>
3356+
{#header_open|Implementation Details#}
3357+
<p>
3358+
To analyze performance cost, there are two cases:
3359+
</p>
3360+
<ul>
3361+
<li>when no errors are returned</li>
3362+
<li>when returning errors</li>
3363+
</ul>
3364+
<p>
3365+
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 <code>void</code> calls a function returning <code>error</code>.
3366+
This is to initialize this struct in the stack memory:
3367+
</p>
3368+
{#code_begin|syntax#}
3369+
pub const StackTrace = struct {
3370+
index: usize,
3371+
instruction_addresses: [N]usize,
3372+
};
3373+
{#code_end#}
3374+
<p>
3375+
Here, N is the maximum function call depth as determined by call graph analysis. Recursion is ignored and counts for 2.
3376+
</p>
3377+
<p>
3378+
A pointer to <code>StackTrace</code> 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.
3379+
</p>
3380+
<p>
3381+
That's it for the path when no errors occur. It's practically free in terms of performance.
3382+
</p>
3383+
<p>
3384+
When generating the code for a function that returns an error, just before the <code>return</code> statement (only for the <code>return</code> statements that return errors), Zig generates a call to this function:
3385+
</p>
3386+
{#code_begin|syntax#}
3387+
// marked as "no-inline" in LLVM IR
3388+
fn __zig_return_error(stack_trace: *StackTrace) void {
3389+
stack_trace.instruction_addresses[stack_trace.index] = @returnAddress();
3390+
stack_trace.index = (stack_trace.index + 1) % N;
3391+
}
3392+
{#code_end#}
3393+
<p>
3394+
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.
3395+
</p>
3396+
<p>
3397+
As for code size cost, 1 function call before a return statement is no big deal. Even so,
3398+
I have <a href="https://github.com/ziglang/zig/issues/690">a plan</a> to make the call to
3399+
<code>__zig_return_error</code> 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.
3400+
</p>
3401+
{#header_close#}
32273402
{#header_close#}
32283403
{#header_close#}
32293404
{#header_open|Optionals#}
@@ -3342,6 +3517,15 @@ test "optional type" {
33423517
// Use compile-time reflection to access the child type of the optional:
33433518
comptime assert(@typeOf(foo).Child == i32);
33443519
}
3520+
{#code_end#}
3521+
{#header_close#}
3522+
{#header_open|null#}
3523+
<p>
3524+
Just like {#link|undefined#}, <code>null</code> has its own type, and the only way to use it is to
3525+
cast it to a different type:
3526+
</p>
3527+
{#code_begin|syntax#}
3528+
const optional_value: ?i32 = null;
33453529
{#code_end#}
33463530
{#header_close#}
33473531
{#header_close#}
@@ -5426,12 +5610,13 @@ pub const TypeInfo = union(TypeId) {
54265610
{#header_close#}
54275611
{#header_open|Build Mode#}
54285612
<p>
5429-
Zig has three build modes:
5613+
Zig has four build modes:
54305614
</p>
54315615
<ul>
54325616
<li>{#link|Debug#} (default)</li>
54335617
<li>{#link|ReleaseFast#}</li>
54345618
<li>{#link|ReleaseSafe#}</li>
5619+
<li>{#link|ReleaseSmall#}</li>
54355620
</ul>
54365621
<p>
54375622
To add standard build options to a <code>build.zig</code> file:
@@ -5448,14 +5633,16 @@ pub fn build(b: &Builder) void {
54485633
<p>
54495634
This causes these options to be available:
54505635
</p>
5451-
<pre><code class="shell"> -Drelease-safe=(bool) optimizations on and safety on
5452-
-Drelease-fast=(bool) optimizations on and safety off</code></pre>
5636+
<pre><code class="shell"> -Drelease-safe=[bool] optimizations on and safety on
5637+
-Drelease-fast=[bool] optimizations on and safety off
5638+
-Drelease-small=[bool] size optimizations on and safety off</code></pre>
54535639
{#header_open|Debug#}
54545640
<pre><code class="shell">$ zig build-exe example.zig</code></pre>
54555641
<ul>
54565642
<li>Fast compilation speed</li>
54575643
<li>Safety checks enabled</li>
54585644
<li>Slow runtime performance</li>
5645+
<li>Large binary size</li>
54595646
</ul>
54605647
{#header_close#}
54615648
{#header_open|ReleaseFast#}
@@ -5464,6 +5651,7 @@ pub fn build(b: &Builder) void {
54645651
<li>Fast runtime performance</li>
54655652
<li>Safety checks disabled</li>
54665653
<li>Slow compilation speed</li>
5654+
<li>Large binary size</li>
54675655
</ul>
54685656
{#header_close#}
54695657
{#header_open|ReleaseSafe#}
@@ -5472,17 +5660,27 @@ pub fn build(b: &Builder) void {
54725660
<li>Medium runtime performance</li>
54735661
<li>Safety checks enabled</li>
54745662
<li>Slow compilation speed</li>
5663+
<li>Large binary size</li>
54755664
</ul>
5476-
{#see_also|Compile Variables|Zig Build System|Undefined Behavior#}
54775665
{#header_close#}
5666+
{#header_open|ReleaseSmall#}
5667+
<pre><code class="shell">$ zig build-exe example.zig --release-small</code></pre>
5668+
<ul>
5669+
<li>Medium runtime performance</li>
5670+
<li>Safety checks disabled</li>
5671+
<li>Slow compilation speed</li>
5672+
<li>Small binary size</li>
5673+
</ul>
5674+
{#header_close#}
5675+
{#see_also|Compile Variables|Zig Build System|Undefined Behavior#}
54785676
{#header_close#}
54795677
{#header_open|Undefined Behavior#}
54805678
<p>
54815679
Zig has many instances of undefined behavior. If undefined behavior is
54825680
detected at compile-time, Zig emits an error. Most undefined behavior that
54835681
cannot be detected at compile-time can be detected at runtime. In these cases,
54845682
Zig has safety checks. Safety checks can be disabled on a per-block basis
5485-
with <code>@setRuntimeSafety</code>. The {#link|ReleaseFast#}
5683+
with {#link|setRuntimeSafety#}. The {#link|ReleaseFast#}
54865684
build mode disables all safety checks in order to facilitate optimizations.
54875685
</p>
54885686
<p>

0 commit comments

Comments
 (0)
Please sign in to comment.