@@ -3908,6 +3908,277 @@ pub fn main() void {
3908
3908
<p>TODO: @fence()</p>
3909
3909
<p>TODO: @atomic rmw</p>
3910
3910
<p>TODO: builtin atomic memory ordering enum</p>
3911
+ {#header_close#}
3912
+ {#header_open|Coroutines#}
3913
+ <p>
3914
+ A coroutine is a generalization of a function.
3915
+ </p>
3916
+ <p>
3917
+ When you call a function, it creates a stack frame,
3918
+ and then the function runs until it reaches a return
3919
+ statement, and then the stack frame is destroyed.
3920
+ At the callsite, the next line of code does not run
3921
+ until the function returns.
3922
+ </p>
3923
+ <p>
3924
+ A coroutine is like a function, but it can be suspended
3925
+ and resumed any number of times, and then it must be
3926
+ explicitly destroyed. When a coroutine suspends, it
3927
+ returns to the resumer.
3928
+ </p>
3929
+ {#header_open|Minimal Coroutine Example#}
3930
+ <p>
3931
+ Declare a coroutine with the <code>async</code> keyword.
3932
+ The expression in angle brackets must evaluate to a struct
3933
+ which has these fields:
3934
+ </p>
3935
+ <ul>
3936
+ <li><code>allocFn: fn (self: *Allocator, byte_count: usize, alignment: u29) Error![]u8</code> - where <code>Error</code> can be any error set.</li>
3937
+ <li><code>freeFn: fn (self: *Allocator, old_mem: []u8) void</code></li>
3938
+ </ul>
3939
+ <p>
3940
+ You may notice that this corresponds to the <code>std.mem.Allocator</code> interface.
3941
+ This makes it convenient to integrate with existing allocators. Note, however,
3942
+ that the language feature does not depend on the standard library, and any struct which
3943
+ has these fields is allowed.
3944
+ </p>
3945
+ <p>
3946
+ Omitting the angle bracket expression when defining an async function makes
3947
+ the function generic. Zig will infer the allocator type when the async function is called.
3948
+ </p>
3949
+ <p>
3950
+ Call a coroutine with the <code>async</code> keyword. Here, the expression in angle brackets
3951
+ is a pointer to the allocator struct that the coroutine expects.
3952
+ </p>
3953
+ <p>
3954
+ The result of an async function call is a <code>promise->T</code> type, where <code>T</code>
3955
+ is the return type of the async function. Once a promise has been created, it must be
3956
+ consumed, either with <code>cancel</code> or <code>await</code>:
3957
+ </p>
3958
+ <p>
3959
+ Async functions start executing when created, so in the following example, the entire
3960
+ async function completes before it is canceled:
3961
+ </p>
3962
+ {#code_begin|test#}
3963
+ const std = @import("std");
3964
+ const assert = std.debug.assert;
3965
+
3966
+ var x: i32 = 1;
3967
+
3968
+ test "create a coroutine and cancel it" {
3969
+ const p = try async<std.debug.global_allocator> simpleAsyncFn();
3970
+ comptime assert(@typeOf(p) == promise->void);
3971
+ cancel p;
3972
+ assert(x == 2);
3973
+ }
3974
+ async<*std.mem.Allocator> fn simpleAsyncFn() void {
3975
+ x += 1;
3976
+ }
3977
+ {#code_end#}
3978
+ {#header_close#}
3979
+ {#header_open|Suspend and Resume#}
3980
+ <p>
3981
+ At any point, an async function may suspend itself. This causes control flow to
3982
+ return to the caller or resumer. The following code demonstrates where control flow
3983
+ goes:
3984
+ </p>
3985
+ {#code_begin|test#}
3986
+ const std = @import("std");
3987
+ const assert = std.debug.assert;
3988
+
3989
+ test "coroutine suspend, resume, cancel" {
3990
+ seq('a');
3991
+ const p = try async<std.debug.global_allocator> testAsyncSeq();
3992
+ seq('c');
3993
+ resume p;
3994
+ seq('f');
3995
+ cancel p;
3996
+ seq('g');
3997
+
3998
+ assert(std.mem.eql(u8, points, "abcdefg"));
3999
+ }
4000
+ async fn testAsyncSeq() void {
4001
+ defer seq('e');
4002
+
4003
+ seq('b');
4004
+ suspend;
4005
+ seq('d');
4006
+ }
4007
+ var points = []u8{0} ** "abcdefg".len;
4008
+ var index: usize = 0;
4009
+
4010
+ fn seq(c: u8) void {
4011
+ points[index] = c;
4012
+ index += 1;
4013
+ }
4014
+ {#code_end#}
4015
+ <p>
4016
+ When an async function suspends itself, it must be sure that it will be
4017
+ resumed or canceled somehow, for example by registering its promise handle
4018
+ in an event loop. Use a suspend capture block to gain access to the
4019
+ promise:
4020
+ </p>
4021
+ {#code_begin|test#}
4022
+ const std = @import("std");
4023
+ const assert = std.debug.assert;
4024
+
4025
+ test "coroutine suspend with block" {
4026
+ const p = try async<std.debug.global_allocator> testSuspendBlock();
4027
+ std.debug.assert(!result);
4028
+ resume a_promise;
4029
+ std.debug.assert(result);
4030
+ cancel p;
4031
+ }
4032
+
4033
+ var a_promise: promise = undefined;
4034
+ var result = false;
4035
+ async fn testSuspendBlock() void {
4036
+ suspend |p| {
4037
+ comptime assert(@typeOf(p) == promise->void);
4038
+ a_promise = p;
4039
+ }
4040
+ result = true;
4041
+ }
4042
+ {#code_end#}
4043
+ <p>
4044
+ Every suspend point in an async function represents a point at which the coroutine
4045
+ could be destroyed. If that happens, <code>defer</code> expressions that are in
4046
+ scope are run, as well as <code>errdefer</code> expressions.
4047
+ </p>
4048
+ <p>
4049
+ {#link|Await#} counts as a suspend point.
4050
+ </p>
4051
+ {#header_open|Breaking from Suspend Blocks#}
4052
+ <p>
4053
+ Suspend blocks support labeled break, just like {#link|while#} and {#link|for#}.
4054
+ </p>
4055
+ <p>
4056
+ Upon entering a <code>suspend</code> block, the coroutine is already considered
4057
+ suspended, and can be resumed. For example, if you started another kernel thread,
4058
+ and had that thread call <code>resume</code> on the promise handle provided by the
4059
+ <code>suspend</code> block, the new thread would begin executing after the suspend
4060
+ block, while the old thread continued executing the suspend block.
4061
+ </p>
4062
+ <p>
4063
+ However, if you use labeled <code>break</code> on the suspend block, the coroutine
4064
+ never returns to its resumer and continues executing.
4065
+ </p>
4066
+ {#code_begin|test#}
4067
+ const std = @import("std");
4068
+ const assert = std.debug.assert;
4069
+
4070
+ test "break from suspend" {
4071
+ var buf: [500]u8 = undefined;
4072
+ var a = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator;
4073
+ var my_result: i32 = 1;
4074
+ const p = try async<a> testBreakFromSuspend(&my_result);
4075
+ cancel p;
4076
+ std.debug.assert(my_result == 2);
4077
+ }
4078
+ async fn testBreakFromSuspend(my_result: *i32) void {
4079
+ s: suspend |p| {
4080
+ break :s;
4081
+ }
4082
+ my_result.* += 1;
4083
+ suspend;
4084
+ my_result.* += 1;
4085
+ }
4086
+ {#code_end#}
4087
+ {#header_close#}
4088
+ {#header_close#}
4089
+ {#header_open|Await#}
4090
+ <p>
4091
+ The <code>await</code> keyword is used to coordinate with an async function's
4092
+ <code>return</code> statement.
4093
+ </p>
4094
+ <p>
4095
+ <code>await</code> is valid only in an <code>async</code> function, and it takes
4096
+ as an operand a promise handle.
4097
+ If the async function associated with the promise handle has already returned,
4098
+ then <code>await</code> destroys the target async function, and gives the return value.
4099
+ Otherwise, <code>await</code> suspends the current async function, registering its
4100
+ promise handle with the target coroutine. It becomes the target coroutine's responsibility
4101
+ to have ensured that it will be resumed or destroyed. When the target coroutine reaches
4102
+ its return statement, it gives the return value to the awaiter, destroys itself, and then
4103
+ resumes the awaiter.
4104
+ </p>
4105
+ <p>
4106
+ A promise handle must be consumed exactly once after it is created, either by <code>cancel</code> or <code>await</code>.
4107
+ </p>
4108
+ <p>
4109
+ <code>await</code> counts as a suspend point, and therefore at every <code>await</code>,
4110
+ a coroutine can be potentially destroyed, which would run <code>defer</code> and <code>errdefer</code> expressions.
4111
+ </p>
4112
+ {#code_begin|test#}
4113
+ const std = @import("std");
4114
+ const assert = std.debug.assert;
4115
+
4116
+ var a_promise: promise = undefined;
4117
+ var final_result: i32 = 0;
4118
+
4119
+ test "coroutine await" {
4120
+ seq('a');
4121
+ const p = async<std.debug.global_allocator> amain() catch unreachable;
4122
+ seq('f');
4123
+ resume a_promise;
4124
+ seq('i');
4125
+ assert(final_result == 1234);
4126
+ assert(std.mem.eql(u8, seq_points, "abcdefghi"));
4127
+ }
4128
+ async fn amain() void {
4129
+ seq('b');
4130
+ const p = async another() catch unreachable;
4131
+ seq('e');
4132
+ final_result = await p;
4133
+ seq('h');
4134
+ }
4135
+ async fn another() i32 {
4136
+ seq('c');
4137
+ suspend |p| {
4138
+ seq('d');
4139
+ a_promise = p;
4140
+ }
4141
+ seq('g');
4142
+ return 1234;
4143
+ }
4144
+
4145
+ var seq_points = []u8{0} ** "abcdefghi".len;
4146
+ var seq_index: usize = 0;
4147
+
4148
+ fn seq(c: u8) void {
4149
+ seq_points[seq_index] = c;
4150
+ seq_index += 1;
4151
+ }
4152
+ {#code_end#}
4153
+ <p>
4154
+ In general, <code>suspend</code> is lower level than <code>await</code>. Most application
4155
+ code will use only <code>async</code> and <code>await</code>, but event loop
4156
+ implementations will make use of <code>suspend</code> internally.
4157
+ </p>
4158
+ {#header_close#}
4159
+ {#header_open|Open Issues#}
4160
+ <p>
4161
+ There are a few issues with coroutines that are considered unresolved. Best be aware of them,
4162
+ as the situation is likely to change before 1.0.0:
4163
+ </p>
4164
+ <ul>
4165
+ <li>Async functions have optimizations disabled - even in release modes - due to an
4166
+ <a href="https://github.com/ziglang/zig/issues/802">LLVM bug</a>.
4167
+ </li>
4168
+ <li>
4169
+ There are some situations where we can know statically that there will not be
4170
+ memory allocation failure, but Zig still forces us to handle it.
4171
+ TODO file an issue for this and link it here.
4172
+ </li>
4173
+ <li>
4174
+ Zig does not take advantage of LLVM's allocation elision optimization for
4175
+ coroutines. It crashed LLVM when I tried to do it the first time. This is
4176
+ related to the other 2 bullet points here. See
4177
+ <a href="https://github.com/ziglang/zig/issues/802">#802</a>.
4178
+ </li>
4179
+ </ul>
4180
+ {#header_close#}
4181
+
3911
4182
{#header_close#}
3912
4183
{#header_open|Builtin Functions#}
3913
4184
<p>
@@ -6124,7 +6395,7 @@ hljs.registerLanguage("zig", function(t) {
6124
6395
},
6125
6396
a = t.IR + "\\s*\\(",
6126
6397
c = {
6127
- 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",
6398
+ 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 ",
6128
6399
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",
6129
6400
literal: "true false null undefined"
6130
6401
},
0 commit comments