authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-06-14 18:12:05-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-06-14 18:12:31-04:00
logf0697c28f80d64c544302aea576e41ebc443b41c
tree2cea03fc579e80ea34a5954e5406bdf55df81564
parentcdf1e366f9c36af111cf41b001a58635d94a7714

langref: docs for error return traces

See #367

1 files changed, 206 insertions(+), 8 deletions(-)

doc/langref.html.in+206-8
......@@ -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#}
595596const 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#}
30043007fn 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#}
30533059fn 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#}
3238pub fn main() !void {
3239 try foo(12);
3240}
3241
3242fn foo(x: i32) !void {
3243 if (x >= 5) {
3244 try bar();
3245 } else {
3246 try bang2();
3247 }
3248}
3249
3250fn 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
3259fn baz() !void {
3260 try bang1();
3261}
3262
3263fn quux() !void {
3264 try bang2();
3265}
3266
3267fn hello() !void {
3268 try bang2();
3269}
3270
3271fn another() !void {
3272 try bang1();
3273}
3274
3275fn bang1() !void {
3276 return error.FileNotFound;
3277}
3278
3279fn 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#}
3292pub fn main() void {
3293 foo(12);
3294}
3295
3296fn foo(x: i32) void {
3297 if (x >= 5) {
3298 bar();
3299 } else {
3300 bang2();
3301 }
3302}
3303
3304fn bar() void {
3305 if (baz()) {
3306 quux();
3307 } else {
3308 hello();
3309 }
3310}
3311
3312fn baz() bool {
3313 return bang1();
3314}
3315
3316fn quux() void {
3317 bang2();
3318}
3319
3320fn hello() void {
3321 bang2();
3322}
3323
3324fn bang1() bool {
3325 return false;
3326}
3327
3328fn 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#}
3369pub 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
3388fn __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#}
3528const 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,9 +5660,19 @@ 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>
......@@ -5482,7 +5680,7 @@ pub fn build(b: &Builder) void {
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>