diff options
| author | Andrew Kelley <superjoe30@gmail.com> | 2018-06-14 18:27:59 -0400 |
|---|---|---|
| committer | Andrew Kelley <superjoe30@gmail.com> | 2018-06-14 18:27:59 -0400 |
| commit | 32dd98b19fe3cc384df32704dac0ff3e377dbe0c (patch) | |
| tree | 2eddf3618d80313bdd24c25bd589bd474f3d82fc /doc | |
| parent | ef7f69d14a017c6c2065e4a376bb8e1f05ace04b (diff) | |
| parent | f0697c28f80d64c544302aea576e41ebc443b41c (diff) | |
| download | zig-32dd98b19fe3cc384df32704dac0ff3e377dbe0c.tar.gz zig-32dd98b19fe3cc384df32704dac0ff3e377dbe0c.zip | |
Merge remote-tracking branch 'origin/master' into llvm7
Diffstat (limited to 'doc')
| -rw-r--r-- | doc/codegen.md | 8 | ||||
| -rw-r--r-- | doc/docgen.zig | 39 | ||||
| -rw-r--r-- | doc/langref.html.in | 1120 |
3 files changed, 641 insertions, 526 deletions
diff --git a/doc/codegen.md b/doc/codegen.md index 02406fae82..65f12f4875 100644 --- a/doc/codegen.md +++ b/doc/codegen.md @@ -6,7 +6,7 @@ Every type has a "handle". If a type is a simple primitive type such as i32 or f64, the handle is "by value", meaning that we pass around the value itself when we refer to a value of that type. -If a type is a container, error union, maybe type, slice, or array, then its +If a type is a container, error union, optional type, slice, or array, then its handle is a pointer, and everywhere we refer to a value of this type we refer to a pointer. @@ -19,7 +19,7 @@ Error union types are represented as: payload: T, } -Maybe types are represented as: +Optional types are represented as: struct { payload: T, @@ -28,6 +28,6 @@ Maybe types are represented as: ## Data Optimizations -Maybe pointer types are special: the 0x0 pointer value is used to represent a -null pointer. Thus, instead of the struct above, maybe pointer types are +Optional pointer types are special: the 0x0 pointer value is used to represent a +null pointer. Thus, instead of the struct above, optional pointer types are represented as a `usize` in codegen and the handle is by value. diff --git a/doc/docgen.zig b/doc/docgen.zig index fed4bb8eba..dfda54567f 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -25,13 +25,13 @@ pub fn main() !void { if (!args_it.skip()) @panic("expected self arg"); - const zig_exe = try (args_it.next(allocator) ?? @panic("expected zig exe arg")); + const zig_exe = try (args_it.next(allocator) orelse @panic("expected zig exe arg")); defer allocator.free(zig_exe); - const in_file_name = try (args_it.next(allocator) ?? @panic("expected input arg")); + const in_file_name = try (args_it.next(allocator) orelse @panic("expected input arg")); defer allocator.free(in_file_name); - const out_file_name = try (args_it.next(allocator) ?? @panic("expected output arg")); + const out_file_name = try (args_it.next(allocator) orelse @panic("expected output arg")); defer allocator.free(out_file_name); var in_file = try os.File.openRead(allocator, in_file_name); @@ -51,14 +51,8 @@ pub fn main() !void { var toc = try genToc(allocator, &tokenizer); try os.makePath(allocator, tmp_dir_name); - defer { - // TODO issue #709 - // disabled to pass CI tests, but obviously we want to implement this - // and then remove this workaround - if (builtin.os != builtin.Os.windows) { - os.deleteTree(allocator, tmp_dir_name) catch {}; - } - } + defer os.deleteTree(allocator, tmp_dir_name) catch {}; + try genHtml(allocator, &tokenizer, &toc, &buffered_out_stream.stream, zig_exe); try buffered_out_stream.flush(); } @@ -300,6 +294,7 @@ const Link = struct { const Node = union(enum) { Content: []const u8, Nav, + Builtin, HeaderOpen: HeaderOpen, SeeAlso: []const SeeAlsoItem, Code: Code, @@ -356,6 +351,9 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc { _ = try eatToken(tokenizer, Token.Id.BracketClose); try nodes.append(Node.Nav); + } else if (mem.eql(u8, tag_name, "builtin")) { + _ = try eatToken(tokenizer, Token.Id.BracketClose); + try nodes.append(Node.Builtin); } else if (mem.eql(u8, tag_name, "header_open")) { _ = try eatToken(tokenizer, Token.Id.Separator); const content_token = try eatToken(tokenizer, Token.Id.TagContent); @@ -690,6 +688,9 @@ fn termColor(allocator: *mem.Allocator, input: []const u8) ![]u8 { fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var, zig_exe: []const u8) !void { var code_progress_index: usize = 0; + + const builtin_code = try escapeHtml(allocator, try getBuiltinCode(allocator, zig_exe)); + for (toc.nodes) |node| { switch (node) { Node.Content => |data| { @@ -704,6 +705,9 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var Node.Nav => { try out.write(toc.toc); }, + Node.Builtin => { + try out.print("<pre><code class=\"zig\">{}</code></pre>", builtin_code); + }, Node.HeaderOpen => |info| { try out.print("<h{} id=\"{}\">{}</h{}>\n", info.n, info.url, info.name, info.n); }, @@ -954,6 +958,9 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var var build_args = std.ArrayList([]const u8).init(allocator); defer build_args.deinit(); + const name_plus_h_ext = try std.fmt.allocPrint(allocator, "{}.h", code.name); + const output_h_file_name = try os.path.join(allocator, tmp_dir_name, name_plus_h_ext); + try build_args.appendSlice([][]const u8{ zig_exe, "build-obj", @@ -962,6 +969,8 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var "on", "--output", tmp_obj_file_name, + "--output-h", + output_h_file_name, }); if (!code.is_inline) { @@ -1060,3 +1069,11 @@ fn exec(allocator: *mem.Allocator, args: []const []const u8) !os.ChildProcess.Ex } return result; } + +fn getBuiltinCode(allocator: *mem.Allocator, zig_exe: []const u8) ![]const u8 { + const result = try exec(allocator, []const []const u8{ + zig_exe, + "builtin", + }); + return result.stdout; +} diff --git a/doc/langref.html.in b/doc/langref.html.in index 4359cadb58..814de721a6 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -156,18 +156,18 @@ pub fn main() void { true or false, !true); - // nullable - var nullable_value: ?[]const u8 = null; - assert(nullable_value == null); + // optional + var optional_value: ?[]const u8 = null; + assert(optional_value == null); - warn("\nnullable 1\ntype: {}\nvalue: {}\n", - @typeName(@typeOf(nullable_value)), nullable_value); + warn("\noptional 1\ntype: {}\nvalue: {}\n", + @typeName(@typeOf(optional_value)), optional_value); - nullable_value = "hi"; - assert(nullable_value != null); + optional_value = "hi"; + assert(optional_value != null); - warn("\nnullable 2\ntype: {}\nvalue: {}\n", - @typeName(@typeOf(nullable_value)), nullable_value); + warn("\noptional 2\ntype: {}\nvalue: {}\n", + @typeName(@typeOf(optional_value)), optional_value); // error union var number_or_error: error!i32 = error.ArgNotFound; @@ -428,7 +428,7 @@ pub fn main() void { </tr> <tr> <td><code>null</code></td> - <td>used to set a nullable type to <code>null</code></td> + <td>used to set an optional type to <code>null</code></td> </tr> <tr> <td><code>undefined</code></td> @@ -440,7 +440,7 @@ pub fn main() void { </tr> </table> </div> - {#see_also|Nullables|this#} + {#see_also|Optionals|this#} {#header_close#} {#header_open|String Literals#} {#code_begin|test#} @@ -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#} @@ -985,10 +987,10 @@ a ^= b</code></pre></td> </td> </tr> <tr> - <td><pre><code class="zig">a ?? b</code></pre></td> + <td><pre><code class="zig">a orelse b</code></pre></td> <td> <ul> - <li>{#link|Nullables#}</li> + <li>{#link|Optionals#}</li> </ul> </td> <td>If <code>a</code> is <code>null</code>, @@ -998,24 +1000,24 @@ a ^= b</code></pre></td> </td> <td> <pre><code class="zig">const value: ?u32 = null; -const unwrapped = value ?? 1234; +const unwrapped = value orelse 1234; unwrapped == 1234</code></pre> </td> </tr> <tr> - <td><pre><code class="zig">??a</code></pre></td> + <td><pre><code class="zig">a.?</code></pre></td> <td> <ul> - <li>{#link|Nullables#}</li> + <li>{#link|Optionals#}</li> </ul> </td> <td> Equivalent to: - <pre><code class="zig">a ?? unreachable</code></pre> + <pre><code class="zig">a orelse unreachable</code></pre> </td> <td> <pre><code class="zig">const value: ?u32 = 5678; -??value == 5678</code></pre> +value.? == 5678</code></pre> </td> </tr> <tr> @@ -1103,7 +1105,7 @@ unwrapped == 1234</code></pre> <td><pre><code class="zig">a == null<code></pre></td> <td> <ul> - <li>{#link|Nullables#}</li> + <li>{#link|Optionals#}</li> </ul> </td> <td> @@ -1261,15 +1263,31 @@ const ptr = &x; x.* == 1234</code></pre> </td> </tr> + <tr> + <td><pre><code class="zig">a || b<code></pre></td> + <td> + <ul> + <li>{#link|Error Set Type#}</li> + </ul> + </td> + <td> + {#link|Merging Error Sets#} + </td> + <td> + <pre><code class="zig">const A = error{One}; +const B = error{Two}; +(A || B) == error{One, Two}</code></pre> + </td> + </tr> </table> </div> {#header_close#} {#header_open|Precedence#} <pre><code>x() x[] x.y a!b -!x -x -%x ~x &x ?x ??x -x{} x.* -! * / % ** *% +!x -x -%x ~x &x ?x +x{} x.* x.? +! * / % ** *% || + - ++ +% -% << >> & @@ -1278,7 +1296,7 @@ x{} x.* == != < > <= >= and or -?? catch +orelse catch = *= /= %= += -= <<= >>= &= ^= |=</code></pre> {#header_close#} {#header_close#} @@ -1483,17 +1501,17 @@ test "volatile" { assert(@typeOf(mmio_ptr) == *volatile u8); } -test "nullable pointers" { - // Pointers cannot be null. If you want a null pointer, use the nullable - // prefix `?` to make the pointer type nullable. +test "optional pointers" { + // Pointers cannot be null. If you want a null pointer, use the optional + // prefix `?` to make the pointer type optional. var ptr: ?*i32 = null; var x: i32 = 1; ptr = &x; - assert((??ptr).* == 1); + assert(ptr.?.* == 1); - // Nullable pointers are the same size as normal pointers, because pointer + // Optional pointers are the same size as normal pointers, because pointer // value 0 is used as the null value. assert(@sizeOf(?*i32) == @sizeOf(*i32)); } @@ -1832,7 +1850,7 @@ test "linked list" { .last = &node, .len = 1, }; - assert((??list2.first).data == 1234); + assert(list2.first.?.data == 1234); } {#code_end#} {#see_also|comptime|@fieldParentPtr#} @@ -2270,7 +2288,7 @@ fn rangeHasNumber(begin: usize, end: usize, number: usize) bool { } test "while null capture" { - // Just like if expressions, while loops can take a nullable as the + // Just like if expressions, while loops can take an optional as the // condition and capture the payload. When null is encountered the loop // exits. var sum1: u32 = 0; @@ -2280,7 +2298,7 @@ test "while null capture" { } assert(sum1 == 3); - // The else branch is allowed on nullable iteration. In this case, it will + // The else branch is allowed on optional iteration. In this case, it will // be executed on the first null value encountered. var sum2: u32 = 0; numbers_left = 3; @@ -2340,7 +2358,7 @@ fn typeNameLength(comptime T: type) usize { return @typeName(T).len; } {#code_end#} - {#see_also|if|Nullables|Errors|comptime|unreachable#} + {#see_also|if|Optionals|Errors|comptime|unreachable#} {#header_close#} {#header_open|for#} {#code_begin|test|for#} @@ -2400,7 +2418,7 @@ test "for else" { if (value == null) { break 9; } else { - sum += ??value; + sum += value.?; } } else blk: { assert(sum == 7); @@ -2461,7 +2479,7 @@ test "if boolean" { assert(result == 47); } -test "if nullable" { +test "if optional" { // If expressions test for null. const a: ?u32 = 0; @@ -2544,7 +2562,7 @@ test "if error union" { } } {#code_end#} - {#see_also|Nullables|Errors#} + {#see_also|Optionals|Errors#} {#header_close#} {#header_open|defer#} {#code_begin|test|defer#} @@ -2983,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 { @@ -2995,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#} @@ -3017,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: @@ -3031,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 { @@ -3046,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> @@ -3062,7 +3085,7 @@ fn createFoo(param: i32) !Foo { // but we want to return it if the function succeeds. errdefer deallocateFoo(foo); - const tmp_buf = allocateTmpBuffer() ?? return error.OutOfMemory; + const tmp_buf = allocateTmpBuffer() orelse return error.OutOfMemory; // tmp_buf is truly a temporary resource, and we for sure want to clean it up // before this block leaves scope defer deallocateTmpBuffer(tmp_buf); @@ -3079,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> @@ -3117,7 +3141,50 @@ test "error union" { comptime assert(@typeOf(foo).ErrorSet == error); } {#code_end#} - <p>TODO the <code>||</code> operator for error sets</p> + {#header_open|Merging Error Sets#} + <p> + Use the <code>||</code> operator to merge two error sets together. The resulting + error set contains the errors of both error sets. Doc comments from the left-hand + side override doc comments from the right-hand side. In this example, the doc + comments for <code>C.PathNotFound</code> is <code>A doc comment</code>. + </p> + <p> + This is especially useful for functions which return different error sets depending + on {#link|comptime#} branches. For example, the Zig standard library uses + <code>LinuxFileOpenError || WindowsFileOpenError</code> for the error set of opening + files. + </p> + {#code_begin|test#} +const A = error{ + NotDir, + + /// A doc comment + PathNotFound, +}; +const B = error{ + OutOfMemory, + + /// B doc comment + PathNotFound, +}; + +const C = A || B; + +fn foo() C!void { + return error.NotDir; +} + +test "merge error sets" { + if (foo()) { + @panic("unexpected"); + } else |err| switch (err) { + error.OutOfMemory => @panic("unexpected"), + error.PathNotFound => @panic("unexpected"), + error.NotDir => {}, + } +} + {#code_end#} + {#header_close#} {#header_open|Inferred Error Sets#} <p> Because many functions in Zig return a possible error, Zig supports inferring the error set. @@ -3164,27 +3231,194 @@ 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_open|Nullables#} + {#header_close#} + {#header_open|Optionals#} <p> One area that Zig provides safety without compromising efficiency or - readability is with the nullable type. + readability is with the optional type. </p> <p> - The question mark symbolizes the nullable type. You can convert a type to a nullable + The question mark symbolizes the optional type. You can convert a type to an optional type by putting a question mark in front of it, like this: </p> {#code_begin|syntax#} // normal integer const normal_int: i32 = 1234; -// nullable integer -const nullable_int: ?i32 = 5678; +// optional integer +const optional_int: ?i32 = 5678; {#code_end#} <p> - Now the variable <code>nullable_int</code> could be an <code>i32</code>, or <code>null</code>. + Now the variable <code>optional_int</code> could be an <code>i32</code>, or <code>null</code>. </p> <p> Instead of integers, let's talk about pointers. Null references are the source of many runtime @@ -3193,8 +3427,8 @@ const nullable_int: ?i32 = 5678; </p> <p>Zig does not have them.</p> <p> - Instead, you can use a nullable pointer. This secretly compiles down to a normal pointer, - since we know we can use 0 as the null value for the nullable type. But the compiler + Instead, you can use an optional pointer. This secretly compiles down to a normal pointer, + since we know we can use 0 as the null value for the optional type. But the compiler can check your work and make sure you don't assign null to something that can't be null. </p> <p> @@ -3219,14 +3453,14 @@ struct Foo *do_a_thing(void) { extern fn malloc(size: size_t) ?*u8; fn doAThing() ?*Foo { - const ptr = malloc(1234) ?? return null; + const ptr = malloc(1234) orelse return null; // ... } {#code_end#} <p> Here, Zig is at least as convenient, if not more, than C. And, the type of "ptr" - is <code>*u8</code> <em>not</em> <code>?*u8</code>. The <code>??</code> operator - unwrapped the nullable type and therefore <code>ptr</code> is guaranteed to be non-null everywhere + is <code>*u8</code> <em>not</em> <code>?*u8</code>. The <code>orelse</code> keyword + unwrapped the optional type and therefore <code>ptr</code> is guaranteed to be non-null everywhere it is used in the function. </p> <p> @@ -3245,10 +3479,10 @@ fn doAThing() ?*Foo { In Zig you can accomplish the same thing: </p> {#code_begin|syntax#} -fn doAThing(nullable_foo: ?*Foo) void { +fn doAThing(optional_foo: ?*Foo) void { // do some stuff - if (nullable_foo) |foo| { + if (optional_foo) |foo| { doSomethingWithFoo(foo); } @@ -3257,7 +3491,7 @@ fn doAThing(nullable_foo: ?*Foo) void { {#code_end#} <p> Once again, the notable thing here is that inside the if block, - <code>foo</code> is no longer a nullable pointer, it is a pointer, which + <code>foo</code> is no longer an optional pointer, it is a pointer, which cannot be null. </p> <p> @@ -3267,24 +3501,33 @@ fn doAThing(nullable_foo: ?*Foo) void { The optimizer can sometimes make better decisions knowing that pointer arguments cannot be null. </p> - {#header_open|Nullable Type#} - <p>A nullable is created by putting <code>?</code> in front of a type. You can use compile-time - reflection to access the child type of a nullable:</p> + {#header_open|Optional Type#} + <p>An optional is created by putting <code>?</code> in front of a type. You can use compile-time + reflection to access the child type of an optional:</p> {#code_begin|test#} const assert = @import("std").debug.assert; -test "nullable type" { - // Declare a nullable and implicitly cast from null: +test "optional type" { + // Declare an optional and implicitly cast from null: var foo: ?i32 = null; - // Implicitly cast from child type of a nullable + // Implicitly cast from child type of an optional foo = 1234; - // Use compile-time reflection to access the child type of the nullable: + // 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#} {#header_open|Casting#} <p>TODO: explain implicit vs explicit casting</p> @@ -3845,9 +4088,6 @@ pub fn printValue(self: *OutStream, value: var) !void { return self.printInt(T, value); } else if (@isFloat(T)) { return self.printFloat(T, value); - } else if (@canImplicitCast([]const u8, value)) { - const casted_value = ([]const u8)(value); - return self.write(casted_value); } else { @compileError("Unable to print type '" ++ @typeName(T) ++ "'"); } @@ -3912,6 +4152,277 @@ pub fn main() void { <p>TODO: @atomic rmw</p> <p>TODO: builtin atomic memory ordering enum</p> {#header_close#} + {#header_open|Coroutines#} + <p> + A coroutine is a generalization of a function. + </p> + <p> + When you call a function, it creates a stack frame, + and then the function runs until it reaches a return + statement, and then the stack frame is destroyed. + At the callsite, the next line of code does not run + until the function returns. + </p> + <p> + A coroutine is like a function, but it can be suspended + and resumed any number of times, and then it must be + explicitly destroyed. When a coroutine suspends, it + returns to the resumer. + </p> + {#header_open|Minimal Coroutine Example#} + <p> + Declare a coroutine with the <code>async</code> keyword. + The expression in angle brackets must evaluate to a struct + which has these fields: + </p> + <ul> + <li><code>allocFn: fn (self: *Allocator, byte_count: usize, alignment: u29) Error![]u8</code> - where <code>Error</code> can be any error set.</li> + <li><code>freeFn: fn (self: *Allocator, old_mem: []u8) void</code></li> + </ul> + <p> + You may notice that this corresponds to the <code>std.mem.Allocator</code> interface. + This makes it convenient to integrate with existing allocators. Note, however, + that the language feature does not depend on the standard library, and any struct which + has these fields is allowed. + </p> + <p> + Omitting the angle bracket expression when defining an async function makes + the function generic. Zig will infer the allocator type when the async function is called. + </p> + <p> + Call a coroutine with the <code>async</code> keyword. Here, the expression in angle brackets + is a pointer to the allocator struct that the coroutine expects. + </p> + <p> + The result of an async function call is a <code>promise->T</code> type, where <code>T</code> + is the return type of the async function. Once a promise has been created, it must be + consumed, either with <code>cancel</code> or <code>await</code>: + </p> + <p> + Async functions start executing when created, so in the following example, the entire + async function completes before it is canceled: + </p> + {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; + +var x: i32 = 1; + +test "create a coroutine and cancel it" { + const p = try async<std.debug.global_allocator> simpleAsyncFn(); + comptime assert(@typeOf(p) == promise->void); + cancel p; + assert(x == 2); +} +async<*std.mem.Allocator> fn simpleAsyncFn() void { + x += 1; +} + {#code_end#} + {#header_close#} + {#header_open|Suspend and Resume#} + <p> + At any point, an async function may suspend itself. This causes control flow to + return to the caller or resumer. The following code demonstrates where control flow + goes: + </p> + {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; + +test "coroutine suspend, resume, cancel" { + seq('a'); + const p = try async<std.debug.global_allocator> testAsyncSeq(); + seq('c'); + resume p; + seq('f'); + cancel p; + seq('g'); + + assert(std.mem.eql(u8, points, "abcdefg")); +} +async fn testAsyncSeq() void { + defer seq('e'); + + seq('b'); + suspend; + seq('d'); +} +var points = []u8{0} ** "abcdefg".len; +var index: usize = 0; + +fn seq(c: u8) void { + points[index] = c; + index += 1; +} + {#code_end#} + <p> + When an async function suspends itself, it must be sure that it will be + resumed or canceled somehow, for example by registering its promise handle + in an event loop. Use a suspend capture block to gain access to the + promise: + </p> + {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; + +test "coroutine suspend with block" { + const p = try async<std.debug.global_allocator> testSuspendBlock(); + std.debug.assert(!result); + resume a_promise; + std.debug.assert(result); + cancel p; +} + +var a_promise: promise = undefined; +var result = false; +async fn testSuspendBlock() void { + suspend |p| { + comptime assert(@typeOf(p) == promise->void); + a_promise = p; + } + result = true; +} + {#code_end#} + <p> + Every suspend point in an async function represents a point at which the coroutine + could be destroyed. If that happens, <code>defer</code> expressions that are in + scope are run, as well as <code>errdefer</code> expressions. + </p> + <p> + {#link|Await#} counts as a suspend point. + </p> + {#header_open|Breaking from Suspend Blocks#} + <p> + Suspend blocks support labeled break, just like {#link|while#} and {#link|for#}. + </p> + <p> + Upon entering a <code>suspend</code> block, the coroutine is already considered + suspended, and can be resumed. For example, if you started another kernel thread, + and had that thread call <code>resume</code> on the promise handle provided by the + <code>suspend</code> block, the new thread would begin executing after the suspend + block, while the old thread continued executing the suspend block. + </p> + <p> + However, if you use labeled <code>break</code> on the suspend block, the coroutine + never returns to its resumer and continues executing. + </p> + {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; + +test "break from suspend" { + var buf: [500]u8 = undefined; + var a = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator; + var my_result: i32 = 1; + const p = try async<a> testBreakFromSuspend(&my_result); + cancel p; + std.debug.assert(my_result == 2); +} +async fn testBreakFromSuspend(my_result: *i32) void { + s: suspend |p| { + break :s; + } + my_result.* += 1; + suspend; + my_result.* += 1; +} + {#code_end#} + {#header_close#} + {#header_close#} + {#header_open|Await#} + <p> + The <code>await</code> keyword is used to coordinate with an async function's + <code>return</code> statement. + </p> + <p> + <code>await</code> is valid only in an <code>async</code> function, and it takes + as an operand a promise handle. + If the async function associated with the promise handle has already returned, + then <code>await</code> destroys the target async function, and gives the return value. + Otherwise, <code>await</code> suspends the current async function, registering its + promise handle with the target coroutine. It becomes the target coroutine's responsibility + to have ensured that it will be resumed or destroyed. When the target coroutine reaches + its return statement, it gives the return value to the awaiter, destroys itself, and then + resumes the awaiter. + </p> + <p> + A promise handle must be consumed exactly once after it is created, either by <code>cancel</code> or <code>await</code>. + </p> + <p> + <code>await</code> counts as a suspend point, and therefore at every <code>await</code>, + a coroutine can be potentially destroyed, which would run <code>defer</code> and <code>errdefer</code> expressions. + </p> + {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; + +var a_promise: promise = undefined; +var final_result: i32 = 0; + +test "coroutine await" { + seq('a'); + const p = async<std.debug.global_allocator> amain() catch unreachable; + seq('f'); + resume a_promise; + seq('i'); + assert(final_result == 1234); + assert(std.mem.eql(u8, seq_points, "abcdefghi")); +} +async fn amain() void { + seq('b'); + const p = async another() catch unreachable; + seq('e'); + final_result = await p; + seq('h'); +} +async fn another() i32 { + seq('c'); + suspend |p| { + seq('d'); + a_promise = p; + } + seq('g'); + return 1234; +} + +var seq_points = []u8{0} ** "abcdefghi".len; +var seq_index: usize = 0; + +fn seq(c: u8) void { + seq_points[seq_index] = c; + seq_index += 1; +} + {#code_end#} + <p> + In general, <code>suspend</code> is lower level than <code>await</code>. Most application + code will use only <code>async</code> and <code>await</code>, but event loop + implementations will make use of <code>suspend</code> internally. + </p> + {#header_close#} + {#header_open|Open Issues#} + <p> + There are a few issues with coroutines that are considered unresolved. Best be aware of them, + as the situation is likely to change before 1.0.0: + </p> + <ul> + <li>Async functions have optimizations disabled - even in release modes - due to an + <a href="https://github.com/ziglang/zig/issues/802">LLVM bug</a>. + </li> + <li> + There are some situations where we can know statically that there will not be + memory allocation failure, but Zig still forces us to handle it. + TODO file an issue for this and link it here. + </li> + <li> + Zig does not take advantage of LLVM's allocation elision optimization for + coroutines. It crashed LLVM when I tried to do it the first time. This is + related to the other 2 bullet points here. See + <a href="https://github.com/ziglang/zig/issues/802">#802</a>. + </li> + </ul> + {#header_close#} + + {#header_close#} {#header_open|Builtin Functions#} <p> Builtin functions are provided by the compiler and are prefixed with <code>@</code>. @@ -4102,12 +4613,6 @@ comptime { </p> {#see_also|Import from C Header File|@cImport|@cDefine|@cInclude#} {#header_close#} - {#header_open|@canImplicitCast#} - <pre><code class="zig">@canImplicitCast(comptime T: type, value) bool</code></pre> - <p> - Returns whether a value can be implicitly casted to a given type. - </p> - {#header_close#} {#header_open|@clz#} <pre><code class="zig">@clz(x: T) U</code></pre> <p> @@ -4897,7 +5402,7 @@ pub const TypeId = enum { ComptimeInt, Undefined, Null, - Nullable, + Optional, ErrorUnion, Error, Enum, @@ -4931,7 +5436,7 @@ pub const TypeInfo = union(TypeId) { ComptimeInt: void, Undefined: void, Null: void, - Nullable: Nullable, + Optional: Optional, ErrorUnion: ErrorUnion, ErrorSet: ErrorSet, Enum: Enum, @@ -4984,7 +5489,7 @@ pub const TypeInfo = union(TypeId) { defs: []Definition, }; - pub const Nullable = struct { + pub const Optional = struct { child: type, }; @@ -5105,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: @@ -5127,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#} @@ -5143,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#} @@ -5151,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> + {#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> - {#see_also|Compile Variables|Zig Build System|Undefined Behavior#} {#header_close#} + {#see_also|Compile Variables|Zig Build System|Undefined Behavior#} {#header_close#} {#header_open|Undefined Behavior#} <p> @@ -5161,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> @@ -5375,8 +5894,8 @@ comptime { <p>At compile-time:</p> {#code_begin|test_err|unable to unwrap null#} comptime { - const nullable_number: ?i32 = null; - const number = ??nullable_number; + const optional_number: ?i32 = null; + const number = optional_number.?; } {#code_end#} <p>At runtime crashes with the message <code>attempt to unwrap null</code> and a stack trace.</p> @@ -5385,9 +5904,9 @@ comptime { {#code_begin|exe|test#} const warn = @import("std").debug.warn; pub fn main() void { - const nullable_number: ?i32 = null; + const optional_number: ?i32 = null; - if (nullable_number) |number| { + if (optional_number) |number| { warn("got number: {}\n", number); } else { warn("it's null\n"); @@ -5474,425 +5993,7 @@ const separator = if (builtin.os == builtin.Os.windows) '\\' else '/'; <p> Example of what is imported with <code>@import("builtin")</code>: </p> - {#code_begin|syntax#} -pub const StackTrace = struct { - index: usize, - instruction_addresses: []usize, -}; - -pub const Os = enum { - freestanding, - ananas, - cloudabi, - dragonfly, - freebsd, - fuchsia, - ios, - kfreebsd, - linux, - lv2, - macosx, - netbsd, - openbsd, - solaris, - windows, - haiku, - minix, - rtems, - nacl, - cnk, - aix, - cuda, - nvcl, - amdhsa, - ps4, - elfiamcu, - tvos, - watchos, - mesa3d, - contiki, - amdpal, - zen, -}; - -pub const Arch = enum { - armv8_3a, - armv8_2a, - armv8_1a, - armv8, - armv8r, - armv8m_baseline, - armv8m_mainline, - armv7, - armv7em, - armv7m, - armv7s, - armv7k, - armv7ve, - armv6, - armv6m, - armv6k, - armv6t2, - armv5, - armv5te, - armv4t, - armebv8_3a, - armebv8_2a, - armebv8_1a, - armebv8, - armebv8r, - armebv8m_baseline, - armebv8m_mainline, - armebv7, - armebv7em, - armebv7m, - armebv7s, - armebv7k, - armebv7ve, - armebv6, - armebv6m, - armebv6k, - armebv6t2, - armebv5, - armebv5te, - armebv4t, - aarch64, - aarch64_be, - arc, - avr, - bpfel, - bpfeb, - hexagon, - mips, - mipsel, - mips64, - mips64el, - msp430, - nios2, - powerpc, - powerpc64, - powerpc64le, - r600, - amdgcn, - riscv32, - riscv64, - sparc, - sparcv9, - sparcel, - s390x, - tce, - tcele, - thumb, - thumbeb, - i386, - x86_64, - xcore, - nvptx, - nvptx64, - le32, - le64, - amdil, - amdil64, - hsail, - hsail64, - spir, - spir64, - kalimbav3, - kalimbav4, - kalimbav5, - shave, - lanai, - wasm32, - wasm64, - renderscript32, - renderscript64, -}; - -pub const Environ = enum { - unknown, - gnu, - gnuabin32, - gnuabi64, - gnueabi, - gnueabihf, - gnux32, - code16, - eabi, - eabihf, - android, - musl, - musleabi, - musleabihf, - msvc, - itanium, - cygnus, - amdopencl, - coreclr, - opencl, - simulator, -}; - -pub const ObjectFormat = enum { - unknown, - coff, - elf, - macho, - wasm, -}; - -pub const GlobalLinkage = enum { - Internal, - Strong, - Weak, - LinkOnce, -}; - -pub const AtomicOrder = enum { - Unordered, - Monotonic, - Acquire, - Release, - AcqRel, - SeqCst, -}; - -pub const AtomicRmwOp = enum { - Xchg, - Add, - Sub, - And, - Nand, - Or, - Xor, - Max, - Min, -}; - -pub const Mode = enum { - Debug, - ReleaseSafe, - ReleaseFast, - ReleaseSmall, -}; - -pub const TypeId = enum { - Type, - Void, - Bool, - NoReturn, - Int, - Float, - Pointer, - Array, - Struct, - ComptimeFloat, - ComptimeInt, - Undefined, - Null, - Nullable, - ErrorUnion, - ErrorSet, - Enum, - Union, - Fn, - Namespace, - Block, - BoundFn, - ArgTuple, - Opaque, - Promise, -}; - -pub const TypeInfo = union(TypeId) { - Type: void, - Void: void, - Bool: void, - NoReturn: void, - Int: Int, - Float: Float, - Pointer: Pointer, - Array: Array, - Struct: Struct, - ComptimeFloat: void, - ComptimeInt: void, - Undefined: void, - Null: void, - Nullable: Nullable, - ErrorUnion: ErrorUnion, - ErrorSet: ErrorSet, - Enum: Enum, - Union: Union, - Fn: Fn, - Namespace: void, - Block: void, - BoundFn: Fn, - ArgTuple: void, - Opaque: void, - Promise: Promise, - - - pub const Int = struct { - is_signed: bool, - bits: u8, - }; - - pub const Float = struct { - bits: u8, - }; - - pub const Pointer = struct { - is_const: bool, - is_volatile: bool, - alignment: u32, - child: type, - }; - - pub const Array = struct { - len: usize, - child: type, - }; - - pub const ContainerLayout = enum { - Auto, - Extern, - Packed, - }; - - pub const StructField = struct { - name: []const u8, - offset: ?usize, - field_type: type, - }; - - pub const Struct = struct { - layout: ContainerLayout, - fields: []StructField, - defs: []Definition, - }; - - pub const Nullable = struct { - child: type, - }; - - pub const ErrorUnion = struct { - error_set: type, - payload: type, - }; - - pub const Error = struct { - name: []const u8, - value: usize, - }; - - pub const ErrorSet = struct { - errors: []Error, - }; - - pub const EnumField = struct { - name: []const u8, - value: usize, - }; - - pub const Enum = struct { - layout: ContainerLayout, - tag_type: type, - fields: []EnumField, - defs: []Definition, - }; - - pub const UnionField = struct { - name: []const u8, - enum_field: ?EnumField, - field_type: type, - }; - - pub const Union = struct { - layout: ContainerLayout, - tag_type: type, - fields: []UnionField, - defs: []Definition, - }; - - pub const CallingConvention = enum { - Unspecified, - C, - Cold, - Naked, - Stdcall, - Async, - }; - - pub const FnArg = struct { - is_generic: bool, - is_noalias: bool, - arg_type: type, - }; - - pub const Fn = struct { - calling_convention: CallingConvention, - is_generic: bool, - is_var_args: bool, - return_type: type, - async_allocator_type: type, - args: []FnArg, - }; - - pub const Promise = struct { - child: type, - }; - - pub const Definition = struct { - name: []const u8, - is_pub: bool, - data: Data, - - pub const Data = union(enum) { - Type: type, - Var: type, - Fn: FnDef, - - pub const FnDef = struct { - fn_type: type, - inline_type: Inline, - calling_convention: CallingConvention, - is_var_args: bool, - is_extern: bool, - is_export: bool, - lib_name: ?[]const u8, - return_type: type, - arg_names: [][] const u8, - - pub const Inline = enum { - Auto, - Always, - Never, - }; - }; - }; - }; -}; - -pub const FloatMode = enum { - Optimized, - Strict, -}; - -pub const Endian = enum { - Big, - Little, -}; - -pub const endian = Endian.Little; -pub const is_test = true; -pub const os = Os.linux; -pub const arch = Arch.x86_64; -pub const environ = Environ.gnu; -pub const object_format = ObjectFormat.elf; -pub const mode = Mode.Debug; -pub const link_libc = false; -pub const have_error_return_tracing = true; -pub const __zig_test_fn_slice = {}; // overwritten later - {#code_end#} + {#builtin#} {#see_also|Build Mode#} {#header_close#} {#header_open|Root Source File#} @@ -6053,8 +6154,7 @@ pub fn build(b: *Builder) void { b.default_step.dependOn(&exe.step); } {#code_end#} - {#header_close#} - {#header_open|Terminal#} + <p class="file">terminal</p> <pre><code class="shell">$ zig build $ ./test all your base are belong to us</code></pre> @@ -6367,9 +6467,9 @@ AsmInputItem = "[" Symbol "]" String "(" Expression ")" AsmClobbers= ":" list(String, ",") -UnwrapExpression = BoolOrExpression (UnwrapNullable | UnwrapError) | BoolOrExpression +UnwrapExpression = BoolOrExpression (UnwrapOptional | UnwrapError) | BoolOrExpression -UnwrapNullable = "??" Expression +UnwrapOptional = "orelse" Expression UnwrapError = "catch" option("|" Symbol "|") Expression @@ -6443,12 +6543,10 @@ MultiplyOperator = "||" | "*" | "/" | "%" | "**" | "*%" PrefixOpExpression = PrefixOp TypeExpr | SuffixOpExpression -SuffixOpExpression = ("async" option("<" SuffixOpExpression ">") SuffixOpExpression FnCallExpression) | PrimaryExpression option(FnCallExpression | ArrayAccessExpression | FieldAccessExpression | SliceExpression | PtrDerefExpression) +SuffixOpExpression = ("async" option("<" SuffixOpExpression ">") SuffixOpExpression FnCallExpression) | PrimaryExpression option(FnCallExpression | ArrayAccessExpression | FieldAccessExpression | SliceExpression | ".*" | ".?") FieldAccessExpression = "." Symbol -PtrDerefExpression = ".*" - FnCallExpression = "(" list(Expression, ",") ")" ArrayAccessExpression = "[" Expression "]" @@ -6461,7 +6559,7 @@ ContainerInitBody = list(StructLiteralField, ",") | list(Expression, ",") StructLiteralField = "." Symbol "=" Expression -PrefixOp = "!" | "-" | "~" | (("*" | "[*]") option("align" "(" Expression option(":" Integer ":" Integer) ")" ) option("const") option("volatile")) | "?" | "??" | "-%" | "try" | "await" +PrefixOp = "!" | "-" | "~" | (("*" | "[*]") option("align" "(" Expression option(":" Integer ":" Integer) ")" ) option("const") option("volatile")) | "?" | "-%" | "try" | "await" PrimaryExpression = Integer | Float | String | CharLiteral | KeywordLiteral | GroupedExpression | BlockExpression(BlockOrExpression) | Symbol | ("@" Symbol FnCallExpression) | ArrayType | FnProto | AsmExpression | ContainerDecl | ("continue" option(":" Symbol)) | ErrorSetDecl | PromiseType @@ -6554,8 +6652,8 @@ hljs.registerLanguage("zig", function(t) { }, a = t.IR + "\\s*\\(", c = { - keyword: "const align var extern stdcallcc nakedcc volatile export pub noalias inline struct packed enum union break return try catch test continue unreachable comptime and or asm defer errdefer if else switch while for fn use bool f32 f64 void type noreturn error i8 u8 i16 u16 i32 u32 i64 u64 isize usize i8w u8w i16w i32w u32w i64w u64w isizew usizew c_short c_ushort c_int c_uint c_long c_ulong c_longlong c_ulonglong", - built_in: "atomicLoad breakpoint returnAddress frameAddress fieldParentPtr setFloatMode IntType OpaqueType compileError compileLog setCold setRuntimeSafety setEvalBranchQuota offsetOf memcpy inlineCall setGlobalLinkage setGlobalSection divTrunc divFloor enumTagName intToPtr ptrToInt panic canImplicitCast ptrCast bitCast rem mod memset sizeOf alignOf alignCast maxValue minValue memberCount memberName memberType typeOf addWithOverflow subWithOverflow mulWithOverflow shlWithOverflow shlExact shrExact cInclude cDefine cUndef ctz clz import cImport errorName embedFile cmpxchgStrong cmpxchgWeak fence divExact truncate atomicRmw sqrt field typeInfo typeName newStackCall", + keyword: "const align var extern stdcallcc nakedcc volatile export pub noalias inline struct packed enum union break return try catch test continue unreachable comptime and or asm defer errdefer if else switch while for fn use bool f32 f64 void type noreturn error i8 u8 i16 u16 i32 u32 i64 u64 isize usize i8w u8w i16w i32w u32w i64w u64w isizew usizew c_short c_ushort c_int c_uint c_long c_ulong c_longlong c_ulonglong resume cancel await async orelse", + built_in: "atomicLoad breakpoint returnAddress frameAddress fieldParentPtr setFloatMode IntType OpaqueType compileError compileLog setCold setRuntimeSafety setEvalBranchQuota offsetOf memcpy inlineCall setGlobalLinkage setGlobalSection divTrunc divFloor enumTagName intToPtr ptrToInt panic ptrCast bitCast rem mod memset sizeOf alignOf alignCast maxValue minValue memberCount memberName memberType typeOf addWithOverflow subWithOverflow mulWithOverflow shlWithOverflow shlExact shrExact cInclude cDefine cUndef ctz clz import cImport errorName embedFile cmpxchgStrong cmpxchgWeak fence divExact truncate atomicRmw sqrt field typeInfo typeName newStackCall", literal: "true false null undefined" }, n = [e, t.CLCM, t.CBCM, s, r]; |
