Commit f0697c28f8
Changed files (1)
doc/langref.html.in
@@ -590,6 +590,7 @@ test "initialization" {
x = 1;
}
{#code_end#}
+ {#header_open|undefined#}
<p>Use <code>undefined</code> to leave variables uninitialized:</p>
{#code_begin|test#}
const assert = @import("std").debug.assert;
@@ -602,6 +603,7 @@ test "init with undefined" {
{#code_end#}
{#header_close#}
{#header_close#}
+ {#header_close#}
{#header_open|Integers#}
{#header_open|Integer Literals#}
{#code_begin|syntax#}
@@ -2999,6 +3001,7 @@ test "parse u64" {
<li>You know with complete certainty it will not return an error, so want to unconditionally unwrap it.</li>
<li>You want to take a different action for each possible error.</li>
</ul>
+ {#header_open|catch#}
<p>If you want to provide a default value, you can use the <code>catch</code> binary operator:</p>
{#code_begin|syntax#}
fn doAThing(str: []u8) void {
@@ -3011,6 +3014,8 @@ fn doAThing(str: []u8) void {
a default value of 13. The type of the right hand side of the binary <code>catch</code> operator must
match the unwrapped error union type, or be of type <code>noreturn</code>.
</p>
+ {#header_close#}
+ {#header_open|try#}
<p>Let's say you wanted to return the error if you got one, otherwise continue with the
function logic:</p>
{#code_begin|syntax#}
@@ -3033,6 +3038,7 @@ fn doAThing(str: []u8) !void {
from the current function with the same error. Otherwise, the expression results in
the unwrapped value.
</p>
+ {#header_close#}
<p>
Maybe you know with complete certainty that an expression will never be an error.
In this case you can do this:
@@ -3047,7 +3053,7 @@ fn doAThing(str: []u8) !void {
</p>
<p>
Finally, you may want to take a different action for every situation. For that, we combine
- the <code>if</code> and <code>switch</code> expression:
+ the {#link|if#} and {#link|switch#} expression:
</p>
{#code_begin|syntax#}
fn doAThing(str: []u8) void {
@@ -3062,9 +3068,10 @@ fn doAThing(str: []u8) void {
}
}
{#code_end#}
+ {#header_open|errdefer#}
<p>
The other component to error handling is defer statements.
- In addition to an unconditional <code>defer</code>, Zig has <code>errdefer</code>,
+ In addition to an unconditional {#link|defer#}, Zig has <code>errdefer</code>,
which evaluates the deferred expression on block exit path if and only if
the function returned with an error from the block.
</p>
@@ -3095,6 +3102,7 @@ fn createFoo(param: i32) !Foo {
the verbosity and cognitive overhead of trying to make sure every exit path
is covered. The deallocation code is always directly following the allocation code.
</p>
+ {#header_close#}
<p>
A couple of other tidbits about error handling:
</p>
@@ -3223,7 +3231,174 @@ test "inferred error set" {
{#header_close#}
{#header_close#}
{#header_open|Error Return Traces#}
- <p>TODO</p>
+ <p>
+ 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.
+ </p>
+ {#code_begin|exe_err#}
+pub fn main() !void {
+ try foo(12);
+}
+
+fn foo(x: i32) !void {
+ if (x >= 5) {
+ try bar();
+ } else {
+ try bang2();
+ }
+}
+
+fn bar() !void {
+ if (baz()) {
+ try quux();
+ } else |err| switch (err) {
+ error.FileNotFound => try hello(),
+ else => try another(),
+ }
+}
+
+fn baz() !void {
+ try bang1();
+}
+
+fn quux() !void {
+ try bang2();
+}
+
+fn hello() !void {
+ try bang2();
+}
+
+fn another() !void {
+ try bang1();
+}
+
+fn bang1() !void {
+ return error.FileNotFound;
+}
+
+fn bang2() !void {
+ return error.PermissionDenied;
+}
+ {#code_end#}
+ <p>
+ Look closely at this example. This is no stack trace.
+ </p>
+ <p>
+ You can see that the final error bubbled up was <code>PermissionDenied</code>,
+ 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,
+ and then returns another one, from the switch statement. Error Return Traces make this clear, whereas a stack trace would look like this:
+ </p>
+ {#code_begin|exe_err#}
+pub fn main() void {
+ foo(12);
+}
+
+fn foo(x: i32) void {
+ if (x >= 5) {
+ bar();
+ } else {
+ bang2();
+ }
+}
+
+fn bar() void {
+ if (baz()) {
+ quux();
+ } else {
+ hello();
+ }
+}
+
+fn baz() bool {
+ return bang1();
+}
+
+fn quux() void {
+ bang2();
+}
+
+fn hello() void {
+ bang2();
+}
+
+fn bang1() bool {
+ return false;
+}
+
+fn bang2() void {
+ @panic("PermissionDenied");
+}
+ {#code_end#}
+ <p>
+ Here, the stack trace does not explain how the control
+ flow in <code>bar</code> got to the <code>hello()</code> call.
+ One would have to open a debugger or further instrument the application
+ in order to find out. The error return trace, on the other hand,
+ shows exactly how the error bubbled up.
+ </p>
+ <p>
+ This debugging feature makes it easier to iterate quickly on code that
+ robustly handles all error conditions. This means that Zig developers
+ will naturally find themselves writing correct, robust code in order
+ to increase their development pace.
+ </p>
+ <p>
+ 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.
+ </p>
+ <p>
+ There are a few ways to activate this error return tracing feature:
+ </p>
+ <ul>
+ <li>Return an error from main</li>
+ <li>An error makes its way to <code>catch unreachable</code> and you have not overridden the default panic handler</li>
+ <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>
+ </ul>
+ {#header_open|Implementation Details#}
+ <p>
+ To analyze performance cost, there are two cases:
+ </p>
+ <ul>
+ <li>when no errors are returned</li>
+ <li>when returning errors</li>
+ </ul>
+ <p>
+ 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>.
+ This is to initialize this struct in the stack memory:
+ </p>
+ {#code_begin|syntax#}
+pub const StackTrace = struct {
+ index: usize,
+ instruction_addresses: [N]usize,
+};
+ {#code_end#}
+ <p>
+ Here, N is the maximum function call depth as determined by call graph analysis. Recursion is ignored and counts for 2.
+ </p>
+ <p>
+ 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.
+ </p>
+ <p>
+ That's it for the path when no errors occur. It's practically free in terms of performance.
+ </p>
+ <p>
+ 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:
+ </p>
+ {#code_begin|syntax#}
+// marked as "no-inline" in LLVM IR
+fn __zig_return_error(stack_trace: *StackTrace) void {
+ stack_trace.instruction_addresses[stack_trace.index] = @returnAddress();
+ stack_trace.index = (stack_trace.index + 1) % N;
+}
+ {#code_end#}
+ <p>
+ 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.
+ </p>
+ <p>
+ As for code size cost, 1 function call before a return statement is no big deal. Even so,
+ I have <a href="https://github.com/ziglang/zig/issues/690">a plan</a> to make the call to
+ <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.
+ </p>
+ {#header_close#}
{#header_close#}
{#header_close#}
{#header_open|Optionals#}
@@ -3342,6 +3517,15 @@ test "optional type" {
// Use compile-time reflection to access the child type of the optional:
comptime assert(@typeOf(foo).Child == i32);
}
+ {#code_end#}
+ {#header_close#}
+ {#header_open|null#}
+ <p>
+ Just like {#link|undefined#}, <code>null</code> has its own type, and the only way to use it is to
+ cast it to a different type:
+ </p>
+ {#code_begin|syntax#}
+const optional_value: ?i32 = null;
{#code_end#}
{#header_close#}
{#header_close#}
@@ -5426,12 +5610,13 @@ pub const TypeInfo = union(TypeId) {
{#header_close#}
{#header_open|Build Mode#}
<p>
- Zig has three build modes:
+ Zig has four build modes:
</p>
<ul>
<li>{#link|Debug#} (default)</li>
<li>{#link|ReleaseFast#}</li>
<li>{#link|ReleaseSafe#}</li>
+ <li>{#link|ReleaseSmall#}</li>
</ul>
<p>
To add standard build options to a <code>build.zig</code> file:
@@ -5448,14 +5633,16 @@ pub fn build(b: &Builder) void {
<p>
This causes these options to be available:
</p>
- <pre><code class="shell"> -Drelease-safe=(bool) optimizations on and safety on
- -Drelease-fast=(bool) optimizations on and safety off</code></pre>
+ <pre><code class="shell"> -Drelease-safe=[bool] optimizations on and safety on
+ -Drelease-fast=[bool] optimizations on and safety off
+ -Drelease-small=[bool] size optimizations on and safety off</code></pre>
{#header_open|Debug#}
<pre><code class="shell">$ zig build-exe example.zig</code></pre>
<ul>
<li>Fast compilation speed</li>
<li>Safety checks enabled</li>
<li>Slow runtime performance</li>
+ <li>Large binary size</li>
</ul>
{#header_close#}
{#header_open|ReleaseFast#}
@@ -5464,6 +5651,7 @@ pub fn build(b: &Builder) void {
<li>Fast runtime performance</li>
<li>Safety checks disabled</li>
<li>Slow compilation speed</li>
+ <li>Large binary size</li>
</ul>
{#header_close#}
{#header_open|ReleaseSafe#}
@@ -5472,9 +5660,19 @@ pub fn build(b: &Builder) void {
<li>Medium runtime performance</li>
<li>Safety checks enabled</li>
<li>Slow compilation speed</li>
+ <li>Large binary size</li>
</ul>
- {#see_also|Compile Variables|Zig Build System|Undefined Behavior#}
{#header_close#}
+ {#header_open|ReleaseSmall#}
+ <pre><code class="shell">$ zig build-exe example.zig --release-small</code></pre>
+ <ul>
+ <li>Medium runtime performance</li>
+ <li>Safety checks disabled</li>
+ <li>Slow compilation speed</li>
+ <li>Small binary size</li>
+ </ul>
+ {#header_close#}
+ {#see_also|Compile Variables|Zig Build System|Undefined Behavior#}
{#header_close#}
{#header_open|Undefined Behavior#}
<p>
@@ -5482,7 +5680,7 @@ pub fn build(b: &Builder) void {
detected at compile-time, Zig emits an error. Most undefined behavior that
cannot be detected at compile-time can be detected at runtime. In these cases,
Zig has safety checks. Safety checks can be disabled on a per-block basis
- with <code>@setRuntimeSafety</code>. The {#link|ReleaseFast#}
+ with {#link|setRuntimeSafety#}. The {#link|ReleaseFast#}
build mode disables all safety checks in order to facilitate optimizations.
</p>
<p>