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" {...@@ -590,6 +590,7 @@ test "initialization" {
590 x = 1;590 x = 1;
591}591}
592 {#code_end#}592 {#code_end#}
593 {#header_open|undefined#}
593 <p>Use <code>undefined</code> to leave variables uninitialized:</p>594 <p>Use <code>undefined</code> to leave variables uninitialized:</p>
594 {#code_begin|test#}595 {#code_begin|test#}
595const assert = @import("std").debug.assert;596const assert = @import("std").debug.assert;
...@@ -602,6 +603,7 @@ test "init with undefined" {...@@ -602,6 +603,7 @@ test "init with undefined" {
602 {#code_end#}603 {#code_end#}
603 {#header_close#}604 {#header_close#}
604 {#header_close#}605 {#header_close#}
606 {#header_close#}
605 {#header_open|Integers#}607 {#header_open|Integers#}
606 {#header_open|Integer Literals#}608 {#header_open|Integer Literals#}
607 {#code_begin|syntax#}609 {#code_begin|syntax#}
...@@ -2999,6 +3001,7 @@ test "parse u64" {...@@ -2999,6 +3001,7 @@ test "parse u64" {
2999 <li>You know with complete certainty it will not return an error, so want to unconditionally unwrap it.</li>3001 <li>You know with complete certainty it will not return an error, so want to unconditionally unwrap it.</li>
3000 <li>You want to take a different action for each possible error.</li>3002 <li>You want to take a different action for each possible error.</li>
3001 </ul>3003 </ul>
3004 {#header_open|catch#}
3002 <p>If you want to provide a default value, you can use the <code>catch</code> binary operator:</p>3005 <p>If you want to provide a default value, you can use the <code>catch</code> binary operator:</p>
3003 {#code_begin|syntax#}3006 {#code_begin|syntax#}
3004fn doAThing(str: []u8) void {3007fn doAThing(str: []u8) void {
...@@ -3011,6 +3014,8 @@ fn doAThing(str: []u8) void {...@@ -3011,6 +3014,8 @@ fn doAThing(str: []u8) void {
3011 a default value of 13. The type of the right hand side of the binary <code>catch</code> operator must3014 a default value of 13. The type of the right hand side of the binary <code>catch</code> operator must
3012 match the unwrapped error union type, or be of type <code>noreturn</code>.3015 match the unwrapped error union type, or be of type <code>noreturn</code>.
3013 </p>3016 </p>
3017 {#header_close#}
3018 {#header_open|try#}
3014 <p>Let's say you wanted to return the error if you got one, otherwise continue with the3019 <p>Let's say you wanted to return the error if you got one, otherwise continue with the
3015 function logic:</p>3020 function logic:</p>
3016 {#code_begin|syntax#}3021 {#code_begin|syntax#}
...@@ -3033,6 +3038,7 @@ fn doAThing(str: []u8) !void {...@@ -3033,6 +3038,7 @@ fn doAThing(str: []u8) !void {
3033 from the current function with the same error. Otherwise, the expression results in3038 from the current function with the same error. Otherwise, the expression results in
3034 the unwrapped value.3039 the unwrapped value.
3035 </p>3040 </p>
3041 {#header_close#}
3036 <p>3042 <p>
3037 Maybe you know with complete certainty that an expression will never be an error.3043 Maybe you know with complete certainty that an expression will never be an error.
3038 In this case you can do this:3044 In this case you can do this:
...@@ -3047,7 +3053,7 @@ fn doAThing(str: []u8) !void {...@@ -3047,7 +3053,7 @@ fn doAThing(str: []u8) !void {
3047 </p>3053 </p>
3048 <p>3054 <p>
3049 Finally, you may want to take a different action for every situation. For that, we combine3055 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:
3051 </p>3057 </p>
3052 {#code_begin|syntax#}3058 {#code_begin|syntax#}
3053fn doAThing(str: []u8) void {3059fn doAThing(str: []u8) void {
...@@ -3062,9 +3068,10 @@ fn doAThing(str: []u8) void {...@@ -3062,9 +3068,10 @@ fn doAThing(str: []u8) void {
3062 }3068 }
3063}3069}
3064 {#code_end#}3070 {#code_end#}
3071 {#header_open|errdefer#}
3065 <p>3072 <p>
3066 The other component to error handling is defer statements.3073 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>,
3068 which evaluates the deferred expression on block exit path if and only if3075 which evaluates the deferred expression on block exit path if and only if
3069 the function returned with an error from the block.3076 the function returned with an error from the block.
3070 </p>3077 </p>
...@@ -3095,6 +3102,7 @@ fn createFoo(param: i32) !Foo {...@@ -3095,6 +3102,7 @@ fn createFoo(param: i32) !Foo {
3095 the verbosity and cognitive overhead of trying to make sure every exit path3102 the verbosity and cognitive overhead of trying to make sure every exit path
3096 is covered. The deallocation code is always directly following the allocation code.3103 is covered. The deallocation code is always directly following the allocation code.
3097 </p>3104 </p>
3105 {#header_close#}
3098 <p>3106 <p>
3099 A couple of other tidbits about error handling:3107 A couple of other tidbits about error handling:
3100 </p>3108 </p>
...@@ -3223,7 +3231,174 @@ test "inferred error set" {...@@ -3223,7 +3231,174 @@ test "inferred error set" {
3223 {#header_close#}3231 {#header_close#}
3224 {#header_close#}3232 {#header_close#}
3225 {#header_open|Error Return Traces#}3233 {#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#}
3227 {#header_close#}3402 {#header_close#}
3228 {#header_close#}3403 {#header_close#}
3229 {#header_open|Optionals#}3404 {#header_open|Optionals#}
...@@ -3342,6 +3517,15 @@ test "optional type" {...@@ -3342,6 +3517,15 @@ test "optional type" {
3342 // Use compile-time reflection to access the child type of the optional:3517 // Use compile-time reflection to access the child type of the optional:
3343 comptime assert(@typeOf(foo).Child == i32);3518 comptime assert(@typeOf(foo).Child == i32);
3344}3519}
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;
3345 {#code_end#}3529 {#code_end#}
3346 {#header_close#}3530 {#header_close#}
3347 {#header_close#}3531 {#header_close#}
...@@ -5426,12 +5610,13 @@ pub const TypeInfo = union(TypeId) {...@@ -5426,12 +5610,13 @@ pub const TypeInfo = union(TypeId) {
5426 {#header_close#}5610 {#header_close#}
5427 {#header_open|Build Mode#}5611 {#header_open|Build Mode#}
5428 <p>5612 <p>
5429 Zig has three build modes:5613 Zig has four build modes:
5430 </p>5614 </p>
5431 <ul>5615 <ul>
5432 <li>{#link|Debug#} (default)</li>5616 <li>{#link|Debug#} (default)</li>
5433 <li>{#link|ReleaseFast#}</li>5617 <li>{#link|ReleaseFast#}</li>
5434 <li>{#link|ReleaseSafe#}</li>5618 <li>{#link|ReleaseSafe#}</li>
5619 <li>{#link|ReleaseSmall#}</li>
5435 </ul>5620 </ul>
5436 <p>5621 <p>
5437 To add standard build options to a <code>build.zig</code> file:5622 To add standard build options to a <code>build.zig</code> file:
...@@ -5448,14 +5633,16 @@ pub fn build(b: &Builder) void {...@@ -5448,14 +5633,16 @@ pub fn build(b: &Builder) void {
5448 <p>5633 <p>
5449 This causes these options to be available:5634 This causes these options to be available:
5450 </p>5635 </p>
5451 <pre><code class="shell"> -Drelease-safe=(bool) optimizations on and safety on5636 <pre><code class="shell"> -Drelease-safe=[bool] optimizations on and safety on
5452 -Drelease-fast=(bool) optimizations on and safety off</code></pre>5637 -Drelease-fast=[bool] optimizations on and safety off
5638 -Drelease-small=[bool] size optimizations on and safety off</code></pre>
5453 {#header_open|Debug#}5639 {#header_open|Debug#}
5454 <pre><code class="shell">$ zig build-exe example.zig</code></pre>5640 <pre><code class="shell">$ zig build-exe example.zig</code></pre>
5455 <ul>5641 <ul>
5456 <li>Fast compilation speed</li>5642 <li>Fast compilation speed</li>
5457 <li>Safety checks enabled</li>5643 <li>Safety checks enabled</li>
5458 <li>Slow runtime performance</li>5644 <li>Slow runtime performance</li>
5645 <li>Large binary size</li>
5459 </ul>5646 </ul>
5460 {#header_close#}5647 {#header_close#}
5461 {#header_open|ReleaseFast#}5648 {#header_open|ReleaseFast#}
...@@ -5464,6 +5651,7 @@ pub fn build(b: &Builder) void {...@@ -5464,6 +5651,7 @@ pub fn build(b: &Builder) void {
5464 <li>Fast runtime performance</li>5651 <li>Fast runtime performance</li>
5465 <li>Safety checks disabled</li>5652 <li>Safety checks disabled</li>
5466 <li>Slow compilation speed</li>5653 <li>Slow compilation speed</li>
5654 <li>Large binary size</li>
5467 </ul>5655 </ul>
5468 {#header_close#}5656 {#header_close#}
5469 {#header_open|ReleaseSafe#}5657 {#header_open|ReleaseSafe#}
...@@ -5472,9 +5660,19 @@ pub fn build(b: &Builder) void {...@@ -5472,9 +5660,19 @@ pub fn build(b: &Builder) void {
5472 <li>Medium runtime performance</li>5660 <li>Medium runtime performance</li>
5473 <li>Safety checks enabled</li>5661 <li>Safety checks enabled</li>
5474 <li>Slow compilation speed</li>5662 <li>Slow compilation speed</li>
5663 <li>Large binary size</li>
5475 </ul>5664 </ul>
5476 {#see_also|Compile Variables|Zig Build System|Undefined Behavior#}
5477 {#header_close#}5665 {#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#}
5478 {#header_close#}5676 {#header_close#}
5479 {#header_open|Undefined Behavior#}5677 {#header_open|Undefined Behavior#}
5480 <p>5678 <p>
...@@ -5482,7 +5680,7 @@ pub fn build(b: &Builder) void {...@@ -5482,7 +5680,7 @@ pub fn build(b: &Builder) void {
5482 detected at compile-time, Zig emits an error. Most undefined behavior that5680 detected at compile-time, Zig emits an error. Most undefined behavior that
5483 cannot be detected at compile-time can be detected at runtime. In these cases,5681 cannot be detected at compile-time can be detected at runtime. In these cases,
5484 Zig has safety checks. Safety checks can be disabled on a per-block basis5682 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#}
5486 build mode disables all safety checks in order to facilitate optimizations.5684 build mode disables all safety checks in order to facilitate optimizations.
5487 </p>5685 </p>
5488 <p>5686 <p>