| ... | @@ -5970,54 +5970,25 @@ test "global assembly" { | ... | @@ -5970,54 +5970,25 @@ test "global assembly" { |
| 5970 | {#header_close#} | 5970 | {#header_close#} |
| 5971 | {#header_open|Async Functions#} | 5971 | {#header_open|Async Functions#} |
| 5972 | <p> | 5972 | <p> |
| 5973 | An async function is a function whose callsite is split into an {#syntax#}async{#endsyntax#} initiation, | 5973 | When a function is called, a frame is pushed to the stack, |
| 5974 | followed by an {#syntax#}await{#endsyntax#} completion. | 5974 | the function runs until it reaches a return statement, and then the frame is popped from the stack. |
| 5975 | </p> | 5975 | At the callsite, the following code does not run until the function returns. |
| 5976 | <p> | | |
| 5977 | When you call a function, it creates a stack frame, | | |
| 5978 | and then the function runs until it reaches a return | | |
| 5979 | statement, and then the stack frame is destroyed. | | |
| 5980 | At the callsite, the next line of code does not run | | |
| 5981 | until the function returns. | | |
| 5982 | </p> | | |
| 5983 | <p> | | |
| 5984 | An async function is like a function, but it can be suspended | | |
| 5985 | and resumed any number of times, and then it must be | | |
| 5986 | explicitly destroyed. When an async function suspends, it | | |
| 5987 | returns to the resumer. | | |
| 5988 | </p> | 5976 | </p> |
| 5989 | {#header_open|Minimal Async Function Example#} | | |
| 5990 | <p> | 5977 | <p> |
| 5991 | Declare an async function with the {#syntax#}async{#endsyntax#} keyword. | 5978 | An async function is a function whose callsite is split into an {#syntax#}async{#endsyntax#} initiation, |
| 5992 | The expression in angle brackets must evaluate to a struct | 5979 | followed by an {#syntax#}await{#endsyntax#} completion. Its frame is |
| 5993 | which has these fields: | 5980 | provided explicitly by the caller, and it can be suspended and resumed any number of times. |
| 5994 | </p> | | |
| 5995 | <ul> | | |
| 5996 | <li>{#syntax#}allocFn: fn (self: *Allocator, byte_count: usize, alignment: u29) Error![]u8{#endsyntax#} - where {#syntax#}Error{#endsyntax#} can be any error set.</li> | | |
| 5997 | <li>{#syntax#}freeFn: fn (self: *Allocator, old_mem: []u8) void{#endsyntax#}</li> | | |
| 5998 | </ul> | | |
| 5999 | <p> | | |
| 6000 | You may notice that this corresponds to the {#syntax#}std.mem.Allocator{#endsyntax#} interface. | | |
| 6001 | This makes it convenient to integrate with existing allocators. Note, however, | | |
| 6002 | that the language feature does not depend on the standard library, and any struct which | | |
| 6003 | has these fields is allowed. | | |
| 6004 | </p> | | |
| 6005 | <p> | | |
| 6006 | Omitting the angle bracket expression when defining an async function makes | | |
| 6007 | the function generic. Zig will infer the allocator type when the async function is called. | | |
| 6008 | </p> | | |
| 6009 | <p> | | |
| 6010 | Call an async function with the {#syntax#}async{#endsyntax#} keyword. Here, the expression in angle brackets | | |
| 6011 | is a pointer to the allocator struct that the async function expects. | | |
| 6012 | </p> | 5981 | </p> |
| 6013 | <p> | 5982 | <p> |
| 6014 | The result of an async function call is a {#syntax#}promise->T{#endsyntax#} type, where {#syntax#}T{#endsyntax#} | 5983 | Zig infers that a function is {#syntax#}async{#endsyntax#} when it observes that the function contains |
| 6015 | is the return type of the async function. Once a promise has been created, it must be | 5984 | a <strong>suspension point</strong>. Async functions can be called the same as normal functions. A |
| 6016 | consumed with {#syntax#}await{#endsyntax#}: | 5985 | function call of an async function is a suspend point. |
| 6017 | </p> | 5986 | </p> |
| | 5987 | {#header_open|Suspend and Resume#} |
| 6018 | <p> | 5988 | <p> |
| 6019 | Async functions start executing when created, so in the following example, the entire | 5989 | At any point, a function may suspend itself. This causes control flow to |
| 6020 | TODO | 5990 | return to the callsite (in the case of the first suspension), |
| | 5991 | or resumer (in the case of subsequent suspensions). |
| 6021 | </p> | 5992 | </p> |
| 6022 | {#code_begin|test#} | 5993 | {#code_begin|test#} |
| 6023 | const std = @import("std"); | 5994 | const std = @import("std"); |
| ... | @@ -6025,32 +5996,25 @@ const assert = std.debug.assert; | ... | @@ -6025,32 +5996,25 @@ const assert = std.debug.assert; |
| 6025 | | 5996 | |
| 6026 | var x: i32 = 1; | 5997 | var x: i32 = 1; |
| 6027 | | 5998 | |
| 6028 | test "call an async function" { | 5999 | test "suspend with no resume" { |
| 6029 | var frame = async simpleAsyncFn(); | 6000 | var frame = async func(); |
| 6030 | comptime assert(@typeOf(frame) == @Frame(simpleAsyncFn)); | | |
| 6031 | assert(x == 2); | 6001 | assert(x == 2); |
| 6032 | } | 6002 | } |
| 6033 | fn simpleAsyncFn() void { | 6003 | |
| | 6004 | fn func() void { |
| 6034 | x += 1; | 6005 | x += 1; |
| 6035 | suspend; | 6006 | suspend; |
| | 6007 | // This line is never reached because the suspend has no matching resume. |
| 6036 | x += 1; | 6008 | x += 1; |
| 6037 | } | 6009 | } |
| 6038 | {#code_end#} | 6010 | {#code_end#} |
| 6039 | {#header_close#} | | |
| 6040 | {#header_open|Suspend and Resume#} | | |
| 6041 | <p> | 6011 | <p> |
| 6042 | At any point, an async function may suspend itself. This causes control flow to | 6012 | In the same way that each allocation should have a corresponding free, |
| 6043 | return to the caller or resumer. The following code demonstrates where control flow | 6013 | Each {#syntax#}suspend{#endsyntax#} should have a corresponding {#syntax#}resume{#endsyntax#}. |
| 6044 | goes: | 6014 | A <strong>suspend block</strong> allows a function to put a pointer to its own |
| 6045 | </p> | 6015 | frame somewhere, for example into an event loop, even if that action will perform a |
| 6046 | <p> | 6016 | {#syntax#}resume{#endsyntax#} operation on a different thread. |
| 6047 | TODO another test example here | 6017 | {#link|@frame#} provides access to the async function frame pointer. |
| 6048 | </p> | | |
| 6049 | <p> | | |
| 6050 | When an async function suspends itself, it must be sure that it will be | | |
| 6051 | resumed somehow, for example by registering its promise handle | | |
| 6052 | in an event loop. Use a suspend capture block to gain access to the | | |
| 6053 | promise (TODO this is outdated): | | |
| 6054 | </p> | 6018 | </p> |
| 6055 | {#code_begin|test#} | 6019 | {#code_begin|test#} |
| 6056 | const std = @import("std"); | 6020 | const std = @import("std"); |
| ... | @@ -6061,9 +6025,9 @@ var result = false; | ... | @@ -6061,9 +6025,9 @@ var result = false; |
| 6061 | | 6025 | |
| 6062 | test "async function suspend with block" { | 6026 | test "async function suspend with block" { |
| 6063 | _ = async testSuspendBlock(); | 6027 | _ = async testSuspendBlock(); |
| 6064 | std.debug.assert(!result); | 6028 | assert(!result); |
| 6065 | resume the_frame; | 6029 | resume the_frame; |
| 6066 | std.debug.assert(result); | 6030 | assert(result); |
| 6067 | } | 6031 | } |
| 6068 | | 6032 | |
| 6069 | fn testSuspendBlock() void { | 6033 | fn testSuspendBlock() void { |
| ... | @@ -6075,19 +6039,15 @@ fn testSuspendBlock() void { | ... | @@ -6075,19 +6039,15 @@ fn testSuspendBlock() void { |
| 6075 | } | 6039 | } |
| 6076 | {#code_end#} | 6040 | {#code_end#} |
| 6077 | <p> | 6041 | <p> |
| 6078 | Every suspend point in an async function represents a point at which the async function | 6042 | {#syntax#}suspend{#endsyntax#} causes a function to be {#syntax#}async{#endsyntax#}. |
| 6079 | could be destroyed. If that happens, {#syntax#}defer{#endsyntax#} expressions that are in | | |
| 6080 | scope are run, as well as {#syntax#}errdefer{#endsyntax#} expressions. | | |
| 6081 | </p> | | |
| 6082 | <p> | | |
| 6083 | {#link|Await#} counts as a suspend point. | | |
| 6084 | </p> | 6043 | </p> |
| | 6044 | |
| 6085 | {#header_open|Resuming from Suspend Blocks#} | 6045 | {#header_open|Resuming from Suspend Blocks#} |
| 6086 | <p> | 6046 | <p> |
| 6087 | Upon entering a {#syntax#}suspend{#endsyntax#} block, the async function is already considered | 6047 | Upon entering a {#syntax#}suspend{#endsyntax#} block, the async function is already considered |
| 6088 | suspended, and can be resumed. For example, if you started another kernel thread, | 6048 | suspended, and can be resumed. For example, if you started another kernel thread, |
| 6089 | and had that thread call {#syntax#}resume{#endsyntax#} on the promise handle provided by the | 6049 | and had that thread call {#syntax#}resume{#endsyntax#} on the frame pointer provided by the |
| 6090 | {#syntax#}suspend{#endsyntax#} block, the new thread would begin executing after the suspend | 6050 | {#link|@frame#}, the new thread would begin executing after the suspend |
| 6091 | block, while the old thread continued executing the suspend block. | 6051 | block, while the old thread continued executing the suspend block. |
| 6092 | </p> | 6052 | </p> |
| 6093 | <p> | 6053 | <p> |
| ... | @@ -6103,7 +6063,7 @@ test "resume from suspend" { | ... | @@ -6103,7 +6063,7 @@ test "resume from suspend" { |
| 6103 | _ = async testResumeFromSuspend(&my_result); | 6063 | _ = async testResumeFromSuspend(&my_result); |
| 6104 | std.debug.assert(my_result == 2); | 6064 | std.debug.assert(my_result == 2); |
| 6105 | } | 6065 | } |
| 6106 | async fn testResumeFromSuspend(my_result: *i32) void { | 6066 | fn testResumeFromSuspend(my_result: *i32) void { |
| 6107 | suspend { | 6067 | suspend { |
| 6108 | resume @frame(); | 6068 | resume @frame(); |
| 6109 | } | 6069 | } |
| ... | @@ -6113,32 +6073,59 @@ async fn testResumeFromSuspend(my_result: *i32) void { | ... | @@ -6113,32 +6073,59 @@ async fn testResumeFromSuspend(my_result: *i32) void { |
| 6113 | } | 6073 | } |
| 6114 | {#code_end#} | 6074 | {#code_end#} |
| 6115 | <p> | 6075 | <p> |
| 6116 | This is guaranteed to be a tail call, and therefore will not cause a new stack frame. | 6076 | This is guaranteed to tail call, and therefore will not cause a new stack frame. |
| 6117 | </p> | 6077 | </p> |
| 6118 | {#header_close#} | 6078 | {#header_close#} |
| 6119 | {#header_close#} | 6079 | {#header_close#} |
| 6120 | {#header_open|Await#} | 6080 | |
| | 6081 | {#header_open|Async and Await#} |
| 6121 | <p> | 6082 | <p> |
| 6122 | The {#syntax#}await{#endsyntax#} keyword is used to coordinate with an async function's | 6083 | In the same way that every {#syntax#}suspend{#endsyntax#} has a matching |
| 6123 | {#syntax#}return{#endsyntax#} statement. | 6084 | {#syntax#}resume{#endsyntax#}, every {#syntax#}async{#endsyntax#} has a matching {#syntax#}await{#endsyntax#}. |
| 6124 | </p> | 6085 | </p> |
| | 6086 | {#code_begin|test#} |
| | 6087 | const std = @import("std"); |
| | 6088 | const assert = std.debug.assert; |
| | 6089 | |
| | 6090 | test "async and await" { |
| | 6091 | // Here we have an exception where we do not match an async |
| | 6092 | // with an await. The test block is not async and so cannot |
| | 6093 | // have a suspend point in it. |
| | 6094 | // This is well-defined behavior, and everything is OK here. |
| | 6095 | // Note however that there would be no way to collect the |
| | 6096 | // return value of amain, if it were something other than void. |
| | 6097 | _ = async amain(); |
| | 6098 | } |
| | 6099 | |
| | 6100 | fn amain() void { |
| | 6101 | var frame = async func(); |
| | 6102 | comptime assert(@typeOf(frame) == @Frame(func)); |
| | 6103 | |
| | 6104 | const ptr: anyframe->void = &frame; |
| | 6105 | const any_ptr: anyframe = ptr; |
| | 6106 | |
| | 6107 | resume any_ptr; |
| | 6108 | await ptr; |
| | 6109 | } |
| | 6110 | |
| | 6111 | fn func() void { |
| | 6112 | suspend; |
| | 6113 | } |
| | 6114 | {#code_end#} |
| 6125 | <p> | 6115 | <p> |
| 6126 | {#syntax#}await{#endsyntax#} is valid only in an {#syntax#}async{#endsyntax#} function, and it takes | 6116 | The {#syntax#}await{#endsyntax#} keyword is used to coordinate with an async function's |
| 6127 | as an operand a promise handle. | 6117 | {#syntax#}return{#endsyntax#} statement. |
| 6128 | If the async function associated with the promise handle has already returned, | | |
| 6129 | then {#syntax#}await{#endsyntax#} destroys the target async function, and gives the return value. | | |
| 6130 | Otherwise, {#syntax#}await{#endsyntax#} suspends the current async function, registering its | | |
| 6131 | promise handle with the target async function. It becomes the target async function's responsibility | | |
| 6132 | to have ensured that it will be resumed or destroyed. When the target async function reaches | | |
| 6133 | its return statement, it gives the return value to the awaiter, destroys itself, and then | | |
| 6134 | resumes the awaiter. | | |
| 6135 | </p> | 6118 | </p> |
| 6136 | <p> | 6119 | <p> |
| 6137 | A frame handle must be consumed exactly once after it is created with {#syntax#}await{#endsyntax#}. | 6120 | {#syntax#}await{#endsyntax#} is a suspend point, and takes as an operand anything that |
| | 6121 | implicitly casts to {#syntax#}anyframe->T{#endsyntax#}. |
| 6138 | </p> | 6122 | </p> |
| 6139 | <p> | 6123 | <p> |
| 6140 | {#syntax#}await{#endsyntax#} counts as a suspend point, and therefore at every {#syntax#}await{#endsyntax#}, | 6124 | There is a common misconception that {#syntax#}await{#endsyntax#} resumes the target function. |
| 6141 | a async function can be potentially destroyed, which would run {#syntax#}defer{#endsyntax#} and {#syntax#}errdefer{#endsyntax#} expressions. | 6125 | It is the other way around: it suspends until the target function completes. |
| | 6126 | In the event that the target function has already completed, {#syntax#}await{#endsyntax#} |
| | 6127 | does not suspend; instead it copies the |
| | 6128 | return value directly from the target function's frame. |
| 6142 | </p> | 6129 | </p> |
| 6143 | {#code_begin|test#} | 6130 | {#code_begin|test#} |
| 6144 | const std = @import("std"); | 6131 | const std = @import("std"); |
| ... | @@ -6156,14 +6143,14 @@ test "async function await" { | ... | @@ -6156,14 +6143,14 @@ test "async function await" { |
| 6156 | assert(final_result == 1234); | 6143 | assert(final_result == 1234); |
| 6157 | assert(std.mem.eql(u8, seq_points, "abcdefghi")); | 6144 | assert(std.mem.eql(u8, seq_points, "abcdefghi")); |
| 6158 | } | 6145 | } |
| 6159 | async fn amain() void { | 6146 | fn amain() void { |
| 6160 | seq('b'); | 6147 | seq('b'); |
| 6161 | var f = async another(); | 6148 | var f = async another(); |
| 6162 | seq('e'); | 6149 | seq('e'); |
| 6163 | final_result = await f; | 6150 | final_result = await f; |
| 6164 | seq('h'); | 6151 | seq('h'); |
| 6165 | } | 6152 | } |
| 6166 | async fn another() i32 { | 6153 | fn another() i32 { |
| 6167 | seq('c'); | 6154 | seq('c'); |
| 6168 | suspend { | 6155 | suspend { |
| 6169 | seq('d'); | 6156 | seq('d'); |
| ... | @@ -6183,31 +6170,156 @@ fn seq(c: u8) void { | ... | @@ -6183,31 +6170,156 @@ fn seq(c: u8) void { |
| 6183 | {#code_end#} | 6170 | {#code_end#} |
| 6184 | <p> | 6171 | <p> |
| 6185 | In general, {#syntax#}suspend{#endsyntax#} is lower level than {#syntax#}await{#endsyntax#}. Most application | 6172 | In general, {#syntax#}suspend{#endsyntax#} is lower level than {#syntax#}await{#endsyntax#}. Most application |
| 6186 | code will use only {#syntax#}async{#endsyntax#} and {#syntax#}await{#endsyntax#}, but event loop | 6173 | code will use only {#syntax#}async{#endsyntax#} and {#syntax#}await{#endsyntax#}, but event loop |
| 6187 | implementations will make use of {#syntax#}suspend{#endsyntax#} internally. | 6174 | implementations will make use of {#syntax#}suspend{#endsyntax#} internally. |
| 6188 | </p> | 6175 | </p> |
| 6189 | {#header_close#} | 6176 | {#header_close#} |
| 6190 | {#header_open|Open Issues#} | 6177 | |
| | 6178 | {#header_open|Async Function Example#} |
| 6191 | <p> | 6179 | <p> |
| 6192 | There are a few issues with async function that are considered unresolved. Best be aware of them, | 6180 | Putting all of this together, here is an example of typical |
| 6193 | as the situation is likely to change before 1.0.0: | 6181 | {#syntax#}async{#endsyntax#}/{#syntax#}await{#endsyntax#} usage: |
| | 6182 | </p> |
| | 6183 | {#code_begin|exe|async#} |
| | 6184 | const std = @import("std"); |
| | 6185 | const Allocator = std.mem.Allocator; |
| | 6186 | |
| | 6187 | pub fn main() void { |
| | 6188 | _ = async amainWrap(); |
| | 6189 | |
| | 6190 | // Typically we would use an event loop to manage resuming async functions, |
| | 6191 | // but in this example we hard code what the event loop would do, |
| | 6192 | // to make things deterministic. |
| | 6193 | resume global_file_frame; |
| | 6194 | resume global_download_frame; |
| | 6195 | } |
| | 6196 | |
| | 6197 | fn amainWrap() void { |
| | 6198 | amain() catch |e| { |
| | 6199 | std.debug.warn("{}\n", e); |
| | 6200 | if (@errorReturnTrace()) |trace| { |
| | 6201 | std.debug.dumpStackTrace(trace.*); |
| | 6202 | } |
| | 6203 | std.process.exit(1); |
| | 6204 | }; |
| | 6205 | } |
| | 6206 | |
| | 6207 | fn amain() !void { |
| | 6208 | const allocator = std.heap.direct_allocator; |
| | 6209 | var download_frame = async fetchUrl(allocator, "https://example.com/"); |
| | 6210 | var awaited_download_frame = false; |
| | 6211 | errdefer if (!awaited_download_frame) { |
| | 6212 | if (await download_frame) |r| allocator.free(r) else |_| {} |
| | 6213 | }; |
| | 6214 | |
| | 6215 | var file_frame = async readFile(allocator, "something.txt"); |
| | 6216 | var awaited_file_frame = false; |
| | 6217 | errdefer if (!awaited_file_frame) { |
| | 6218 | if (await file_frame) |r| allocator.free(r) else |_| {} |
| | 6219 | }; |
| | 6220 | |
| | 6221 | awaited_file_frame = true; |
| | 6222 | const file_text = try await file_frame; |
| | 6223 | defer allocator.free(file_text); |
| | 6224 | |
| | 6225 | awaited_download_frame = true; |
| | 6226 | const download_text = try await download_frame; |
| | 6227 | defer allocator.free(download_text); |
| | 6228 | |
| | 6229 | std.debug.warn("download_text: {}\n", download_text); |
| | 6230 | std.debug.warn("file_text: {}\n", file_text); |
| | 6231 | } |
| | 6232 | |
| | 6233 | var global_download_frame: anyframe = undefined; |
| | 6234 | fn fetchUrl(allocator: *Allocator, url: []const u8) ![]u8 { |
| | 6235 | const result = try std.mem.dupe(allocator, u8, "this is the downloaded url contents"); |
| | 6236 | errdefer allocator.free(result); |
| | 6237 | suspend { |
| | 6238 | global_download_frame = @frame(); |
| | 6239 | } |
| | 6240 | std.debug.warn("fetchUrl returning\n"); |
| | 6241 | return result; |
| | 6242 | } |
| | 6243 | |
| | 6244 | var global_file_frame: anyframe = undefined; |
| | 6245 | fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 { |
| | 6246 | const result = try std.mem.dupe(allocator, u8, "this is the file contents"); |
| | 6247 | errdefer allocator.free(result); |
| | 6248 | suspend { |
| | 6249 | global_file_frame = @frame(); |
| | 6250 | } |
| | 6251 | std.debug.warn("readFile returning\n"); |
| | 6252 | return result; |
| | 6253 | } |
| | 6254 | {#code_end#} |
| | 6255 | <p> |
| | 6256 | Now we remove the {#syntax#}suspend{#endsyntax#} and {#syntax#}resume{#endsyntax#} code, and |
| | 6257 | observe the same behavior, with one tiny difference: |
| | 6258 | </p> |
| | 6259 | {#code_begin|exe|blocking#} |
| | 6260 | const std = @import("std"); |
| | 6261 | const Allocator = std.mem.Allocator; |
| | 6262 | |
| | 6263 | pub fn main() void { |
| | 6264 | _ = async amainWrap(); |
| | 6265 | } |
| | 6266 | |
| | 6267 | fn amainWrap() void { |
| | 6268 | amain() catch |e| { |
| | 6269 | std.debug.warn("{}\n", e); |
| | 6270 | if (@errorReturnTrace()) |trace| { |
| | 6271 | std.debug.dumpStackTrace(trace.*); |
| | 6272 | } |
| | 6273 | std.process.exit(1); |
| | 6274 | }; |
| | 6275 | } |
| | 6276 | |
| | 6277 | fn amain() !void { |
| | 6278 | const allocator = std.heap.direct_allocator; |
| | 6279 | var download_frame = async fetchUrl(allocator, "https://example.com/"); |
| | 6280 | var awaited_download_frame = false; |
| | 6281 | errdefer if (!awaited_download_frame) { |
| | 6282 | if (await download_frame) |r| allocator.free(r) else |_| {} |
| | 6283 | }; |
| | 6284 | |
| | 6285 | var file_frame = async readFile(allocator, "something.txt"); |
| | 6286 | var awaited_file_frame = false; |
| | 6287 | errdefer if (!awaited_file_frame) { |
| | 6288 | if (await file_frame) |r| allocator.free(r) else |_| {} |
| | 6289 | }; |
| | 6290 | |
| | 6291 | awaited_file_frame = true; |
| | 6292 | const file_text = try await file_frame; |
| | 6293 | defer allocator.free(file_text); |
| | 6294 | |
| | 6295 | awaited_download_frame = true; |
| | 6296 | const download_text = try await download_frame; |
| | 6297 | defer allocator.free(download_text); |
| | 6298 | |
| | 6299 | std.debug.warn("download_text: {}\n", download_text); |
| | 6300 | std.debug.warn("file_text: {}\n", file_text); |
| | 6301 | } |
| | 6302 | |
| | 6303 | fn fetchUrl(allocator: *Allocator, url: []const u8) ![]u8 { |
| | 6304 | const result = try std.mem.dupe(allocator, u8, "this is the downloaded url contents"); |
| | 6305 | errdefer allocator.free(result); |
| | 6306 | std.debug.warn("fetchUrl returning\n"); |
| | 6307 | return result; |
| | 6308 | } |
| | 6309 | |
| | 6310 | fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 { |
| | 6311 | const result = try std.mem.dupe(allocator, u8, "this is the file contents"); |
| | 6312 | errdefer allocator.free(result); |
| | 6313 | std.debug.warn("readFile returning\n"); |
| | 6314 | return result; |
| | 6315 | } |
| | 6316 | {#code_end#} |
| | 6317 | <p> |
| | 6318 | Previously, the {#syntax#}fetchUrl{#endsyntax#} and {#syntax#}readFile{#endsyntax#} functions suspended, |
| | 6319 | and were resumed in an order determined by the {#syntax#}main{#endsyntax#} function. Now, |
| | 6320 | since there are no suspend points, the order of the printed "... returning" messages |
| | 6321 | is determined by the order of {#syntax#}async{#endsyntax#} callsites. |
| 6194 | </p> | 6322 | </p> |
| 6195 | <ul> | | |
| 6196 | <li>Async functions have optimizations disabled - even in release modes - due to an | | |
| 6197 | <a href="https://github.com/ziglang/zig/issues/802">LLVM bug</a>. | | |
| 6198 | </li> | | |
| 6199 | <li> | | |
| 6200 | There are some situations where we can know statically that there will not be | | |
| 6201 | memory allocation failure, but Zig still forces us to handle it. | | |
| 6202 | TODO file an issue for this and link it here. | | |
| 6203 | </li> | | |
| 6204 | <li> | | |
| 6205 | Zig does not take advantage of LLVM's allocation elision optimization for | | |
| 6206 | async function. It crashed LLVM when I tried to do it the first time. This is | | |
| 6207 | related to the other 2 bullet points here. See | | |
| 6208 | <a href="https://github.com/ziglang/zig/issues/802">#802</a>. | | |
| 6209 | </li> | | |
| 6210 | </ul> | | |
| 6211 | {#header_close#} | 6323 | {#header_close#} |
| 6212 | | 6324 | |
| 6213 | {#header_close#} | 6325 | {#header_close#} |
| ... | @@ -6265,6 +6377,49 @@ comptime { | ... | @@ -6265,6 +6377,49 @@ comptime { |
| 6265 | Note: This function is deprecated. Use {#link|@typeInfo#} instead. | 6377 | Note: This function is deprecated. Use {#link|@typeInfo#} instead. |
| 6266 | </p> | 6378 | </p> |
| 6267 | {#header_close#} | 6379 | {#header_close#} |
| | 6380 | |
| | 6381 | {#header_open|@asyncCall#} |
| | 6382 | <pre>{#syntax#}@asyncCall(frame_buffer: []u8, result_ptr, function_ptr, args: ...) anyframe->T{#endsyntax#}</pre> |
| | 6383 | <p> |
| | 6384 | {#syntax#}@asyncCall{#endsyntax#} performs an {#syntax#}async{#endsyntax#} call on a function pointer, |
| | 6385 | which may or may not be an {#link|async function|Async Functions#}. |
| | 6386 | </p> |
| | 6387 | <p> |
| | 6388 | The provided {#syntax#}frame_buffer{#endsyntax#} must be large enough to fit the entire function frame. |
| | 6389 | This size can be determined with {#link|@frameSize#}. To provide a too-small buffer |
| | 6390 | invokes safety-checked {#link|Undefined Behavior#}. |
| | 6391 | </p> |
| | 6392 | <p> |
| | 6393 | {#syntax#}result_ptr{#endsyntax#} is optional ({#link|null#} may be provided). If provided, |
| | 6394 | the function call will write its result directly to the result pointer, which will be available to |
| | 6395 | read after {#link|await|Async and Await#} completes. Any result location provided to |
| | 6396 | {#syntax#}await{#endsyntax#} will copy the result from {#syntax#}result_ptr{#endsyntax#}. |
| | 6397 | </p> |
| | 6398 | {#code_begin|test#} |
| | 6399 | const std = @import("std"); |
| | 6400 | const assert = std.debug.assert; |
| | 6401 | |
| | 6402 | test "async fn pointer in a struct field" { |
| | 6403 | var data: i32 = 1; |
| | 6404 | const Foo = struct { |
| | 6405 | bar: async fn (*i32) void, |
| | 6406 | }; |
| | 6407 | var foo = Foo{ .bar = func }; |
| | 6408 | var bytes: [64]u8 = undefined; |
| | 6409 | const f = @asyncCall(&bytes, {}, foo.bar, &data); |
| | 6410 | assert(data == 2); |
| | 6411 | resume f; |
| | 6412 | assert(data == 4); |
| | 6413 | } |
| | 6414 | |
| | 6415 | async fn func(y: *i32) void { |
| | 6416 | defer y.* += 2; |
| | 6417 | y.* += 1; |
| | 6418 | suspend; |
| | 6419 | } |
| | 6420 | {#code_end#} |
| | 6421 | {#header_close#} |
| | 6422 | |
| 6268 | {#header_open|@atomicLoad#} | 6423 | {#header_open|@atomicLoad#} |
| 6269 | <pre>{#syntax#}@atomicLoad(comptime T: type, ptr: *const T, comptime ordering: builtin.AtomicOrder) T{#endsyntax#}</pre> | 6424 | <pre>{#syntax#}@atomicLoad(comptime T: type, ptr: *const T, comptime ordering: builtin.AtomicOrder) T{#endsyntax#}</pre> |
| 6270 | <p> | 6425 | <p> |
| ... | @@ -6855,6 +7010,44 @@ export fn @"A function name that is a complete sentence."() void {} | ... | @@ -6855,6 +7010,44 @@ export fn @"A function name that is a complete sentence."() void {} |
| 6855 | {#see_also|@intToFloat#} | 7010 | {#see_also|@intToFloat#} |
| 6856 | {#header_close#} | 7011 | {#header_close#} |
| 6857 | | 7012 | |
| | 7013 | {#header_open|@frame#} |
| | 7014 | <pre>{#syntax#}@frame() *@Frame(func){#endsyntax#}</pre> |
| | 7015 | <p> |
| | 7016 | This function returns a pointer to the frame for a given function. This type |
| | 7017 | can be {#link|implicitly cast|Implicit Casts#} to {#syntax#}anyframe->T{#endsyntax#} and |
| | 7018 | to {#syntax#}anyframe{#endsyntax#}, where {#syntax#}T{#endsyntax#} is the return type |
| | 7019 | of the function in scope. |
| | 7020 | </p> |
| | 7021 | <p> |
| | 7022 | This function does not mark a suspension point, but it does cause the function in scope |
| | 7023 | to become an {#link|async function|Async Functions#}. |
| | 7024 | </p> |
| | 7025 | {#header_close#} |
| | 7026 | |
| | 7027 | {#header_open|@Frame#} |
| | 7028 | <pre>{#syntax#}@Frame(func: var) type{#endsyntax#}</pre> |
| | 7029 | <p> |
| | 7030 | This function returns the frame type of a function. This works for {#link|Async Functions#} |
| | 7031 | as well as any function without a specific calling convention. |
| | 7032 | </p> |
| | 7033 | <p> |
| | 7034 | This type is suitable to be used as the return type of {#link|async|Async and Await#} which |
| | 7035 | allows one to, for example, heap-allocate an async function frame: |
| | 7036 | </p> |
| | 7037 | {#code_begin|test#} |
| | 7038 | const std = @import("std"); |
| | 7039 | |
| | 7040 | test "heap allocated frame" { |
| | 7041 | const frame = try std.heap.direct_allocator.create(@Frame(func)); |
| | 7042 | frame.* = async func(); |
| | 7043 | } |
| | 7044 | |
| | 7045 | fn func() void { |
| | 7046 | suspend; |
| | 7047 | } |
| | 7048 | {#code_end#} |
| | 7049 | {#header_close#} |
| | 7050 | |
| 6858 | {#header_open|@frameAddress#} | 7051 | {#header_open|@frameAddress#} |
| 6859 | <pre>{#syntax#}@frameAddress() usize{#endsyntax#}</pre> | 7052 | <pre>{#syntax#}@frameAddress() usize{#endsyntax#}</pre> |
| 6860 | <p> | 7053 | <p> |
| ... | @@ -6870,14 +7063,14 @@ export fn @"A function name that is a complete sentence."() void {} | ... | @@ -6870,14 +7063,14 @@ export fn @"A function name that is a complete sentence."() void {} |
| 6870 | </p> | 7063 | </p> |
| 6871 | {#header_close#} | 7064 | {#header_close#} |
| 6872 | | 7065 | |
| 6873 | {#header_open|@handle#} | 7066 | {#header_open|@frameSize#} |
| 6874 | <pre>{#syntax#}@handle(){#endsyntax#}</pre> | 7067 | <pre>{#syntax#}@frameSize() usize{#endsyntax#}</pre> |
| 6875 | <p> | 7068 | <p> |
| 6876 | This function returns a {#syntax#}promise->T{#endsyntax#} type, where {#syntax#}T{#endsyntax#} | 7069 | This is the same as {#syntax#}@sizeOf(@Frame(func)){#endsyntax#}, where {#syntax#}func{#endsyntax#} |
| 6877 | is the return type of the async function in scope. | 7070 | may be runtime-known. |
| 6878 | </p> | 7071 | </p> |
| 6879 | <p> | 7072 | <p> |
| 6880 | This function is only valid within an async function scope. | 7073 | This function is typically used in conjunction with {#link|@asyncCall#}. |
| 6881 | </p> | 7074 | </p> |
| 6882 | {#header_close#} | 7075 | {#header_close#} |
| 6883 | | 7076 | |