You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
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
0 commit comments