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