| ... | ... | @@ -5970,54 +5970,25 @@ test "global assembly" { |
| 5970 | 5970 | {#header_close#} |
| 5971 | 5971 | {#header_open|Async Functions#} |
| 5972 | 5972 | <p> |
| 5973 | | An async function is a function whose callsite is split into an {#syntax#}async{#endsyntax#} initiation, |
| 5974 | | followed by an {#syntax#}await{#endsyntax#} completion. |
| 5975 | | </p> |
| 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. |
| 5973 | When a function is called, a frame is pushed to the stack, |
| 5974 | the function runs until it reaches a return statement, and then the frame is popped from the stack. |
| 5975 | At the callsite, the following code does not run until the function returns. |
| 5988 | 5976 | </p> |
| 5989 | | {#header_open|Minimal Async Function Example#} |
| 5990 | 5977 | <p> |
| 5991 | | Declare an async function with the {#syntax#}async{#endsyntax#} keyword. |
| 5992 | | The expression in angle brackets must evaluate to a struct |
| 5993 | | which has these fields: |
| 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. |
| 5978 | An async function is a function whose callsite is split into an {#syntax#}async{#endsyntax#} initiation, |
| 5979 | followed by an {#syntax#}await{#endsyntax#} completion. Its frame is |
| 5980 | provided explicitly by the caller, and it can be suspended and resumed any number of times. |
| 6012 | 5981 | </p> |
| 6013 | 5982 | <p> |
| 6014 | | The result of an async function call is a {#syntax#}promise->T{#endsyntax#} type, where {#syntax#}T{#endsyntax#} |
| 6015 | | is the return type of the async function. Once a promise has been created, it must be |
| 6016 | | consumed with {#syntax#}await{#endsyntax#}: |
| 5983 | Zig infers that a function is {#syntax#}async{#endsyntax#} when it observes that the function contains |
| 5984 | a <strong>suspension point</strong>. Async functions can be called the same as normal functions. A |
| 5985 | function call of an async function is a suspend point. |
| 6017 | 5986 | </p> |
| 5987 | {#header_open|Suspend and Resume#} |
| 6018 | 5988 | <p> |
| 6019 | | Async functions start executing when created, so in the following example, the entire |
| 6020 | | TODO |
| 5989 | At any point, a function may suspend itself. This causes control flow to |
| 5990 | return to the callsite (in the case of the first suspension), |
| 5991 | or resumer (in the case of subsequent suspensions). |
| 6021 | 5992 | </p> |
| 6022 | 5993 | {#code_begin|test#} |
| 6023 | 5994 | const std = @import("std"); |
| ... | ... | @@ -6025,32 +5996,25 @@ const assert = std.debug.assert; |
| 6025 | 5996 | |
| 6026 | 5997 | var x: i32 = 1; |
| 6027 | 5998 | |
| 6028 | | test "call an async function" { |
| 6029 | | var frame = async simpleAsyncFn(); |
| 6030 | | comptime assert(@typeOf(frame) == @Frame(simpleAsyncFn)); |
| 5999 | test "suspend with no resume" { |
| 6000 | var frame = async func(); |
| 6031 | 6001 | assert(x == 2); |
| 6032 | 6002 | } |
| 6033 | | fn simpleAsyncFn() void { |
| 6003 | |
| 6004 | fn func() void { |
| 6034 | 6005 | x += 1; |
| 6035 | 6006 | suspend; |
| 6007 | // This line is never reached because the suspend has no matching resume. |
| 6036 | 6008 | x += 1; |
| 6037 | 6009 | } |
| 6038 | 6010 | {#code_end#} |
| 6039 | | {#header_close#} |
| 6040 | | {#header_open|Suspend and Resume#} |
| 6041 | 6011 | <p> |
| 6042 | | At any point, an async function may suspend itself. This causes control flow to |
| 6043 | | return to the caller or resumer. The following code demonstrates where control flow |
| 6044 | | goes: |
| 6045 | | </p> |
| 6046 | | <p> |
| 6047 | | TODO another test example here |
| 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): |
| 6012 | In the same way that each allocation should have a corresponding free, |
| 6013 | Each {#syntax#}suspend{#endsyntax#} should have a corresponding {#syntax#}resume{#endsyntax#}. |
| 6014 | A <strong>suspend block</strong> allows a function to put a pointer to its own |
| 6015 | frame somewhere, for example into an event loop, even if that action will perform a |
| 6016 | {#syntax#}resume{#endsyntax#} operation on a different thread. |
| 6017 | {#link|@frame#} provides access to the async function frame pointer. |
| 6054 | 6018 | </p> |
| 6055 | 6019 | {#code_begin|test#} |
| 6056 | 6020 | const std = @import("std"); |
| ... | ... | @@ -6061,9 +6025,9 @@ var result = false; |
| 6061 | 6025 | |
| 6062 | 6026 | test "async function suspend with block" { |
| 6063 | 6027 | _ = async testSuspendBlock(); |
| 6064 | | std.debug.assert(!result); |
| 6028 | assert(!result); |
| 6065 | 6029 | resume the_frame; |
| 6066 | | std.debug.assert(result); |
| 6030 | assert(result); |
| 6067 | 6031 | } |
| 6068 | 6032 | |
| 6069 | 6033 | fn testSuspendBlock() void { |
| ... | ... | @@ -6075,19 +6039,15 @@ fn testSuspendBlock() void { |
| 6075 | 6039 | } |
| 6076 | 6040 | {#code_end#} |
| 6077 | 6041 | <p> |
| 6078 | | Every suspend point in an async function represents a point at which the async function |
| 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. |
| 6042 | {#syntax#}suspend{#endsyntax#} causes a function to be {#syntax#}async{#endsyntax#}. |
| 6084 | 6043 | </p> |
| 6044 | |
| 6085 | 6045 | {#header_open|Resuming from Suspend Blocks#} |
| 6086 | 6046 | <p> |
| 6087 | 6047 | Upon entering a {#syntax#}suspend{#endsyntax#} block, the async function is already considered |
| 6088 | 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 |
| 6090 | | {#syntax#}suspend{#endsyntax#} block, the new thread would begin executing after the suspend |
| 6049 | and had that thread call {#syntax#}resume{#endsyntax#} on the frame pointer provided by the |
| 6050 | {#link|@frame#}, the new thread would begin executing after the suspend |
| 6091 | 6051 | block, while the old thread continued executing the suspend block. |
| 6092 | 6052 | </p> |
| 6093 | 6053 | <p> |
| ... | ... | @@ -6103,7 +6063,7 @@ test "resume from suspend" { |
| 6103 | 6063 | _ = async testResumeFromSuspend(&my_result); |
| 6104 | 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 | 6067 | suspend { |
| 6108 | 6068 | resume @frame(); |
| 6109 | 6069 | } |
| ... | ... | @@ -6113,32 +6073,59 @@ async fn testResumeFromSuspend(my_result: *i32) void { |
| 6113 | 6073 | } |
| 6114 | 6074 | {#code_end#} |
| 6115 | 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 | 6077 | </p> |
| 6118 | 6078 | {#header_close#} |
| 6119 | 6079 | {#header_close#} |
| 6120 | | {#header_open|Await#} |
| 6080 | |
| 6081 | {#header_open|Async and Await#} |
| 6121 | 6082 | <p> |
| 6122 | | The {#syntax#}await{#endsyntax#} keyword is used to coordinate with an async function's |
| 6123 | | {#syntax#}return{#endsyntax#} statement. |
| 6083 | In the same way that every {#syntax#}suspend{#endsyntax#} has a matching |
| 6084 | {#syntax#}resume{#endsyntax#}, every {#syntax#}async{#endsyntax#} has a matching {#syntax#}await{#endsyntax#}. |
| 6124 | 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 | 6115 | <p> |
| 6126 | | {#syntax#}await{#endsyntax#} is valid only in an {#syntax#}async{#endsyntax#} function, and it takes |
| 6127 | | as an operand a promise handle. |
| 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. |
| 6116 | The {#syntax#}await{#endsyntax#} keyword is used to coordinate with an async function's |
| 6117 | {#syntax#}return{#endsyntax#} statement. |
| 6135 | 6118 | </p> |
| 6136 | 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 | 6122 | </p> |
| 6139 | 6123 | <p> |
| 6140 | | {#syntax#}await{#endsyntax#} counts as a suspend point, and therefore at every {#syntax#}await{#endsyntax#}, |
| 6141 | | a async function can be potentially destroyed, which would run {#syntax#}defer{#endsyntax#} and {#syntax#}errdefer{#endsyntax#} expressions. |
| 6124 | There is a common misconception that {#syntax#}await{#endsyntax#} resumes the target function. |
| 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 | 6129 | </p> |
| 6143 | 6130 | {#code_begin|test#} |
| 6144 | 6131 | const std = @import("std"); |
| ... | ... | @@ -6156,14 +6143,14 @@ test "async function await" { |
| 6156 | 6143 | assert(final_result == 1234); |
| 6157 | 6144 | assert(std.mem.eql(u8, seq_points, "abcdefghi")); |
| 6158 | 6145 | } |
| 6159 | | async fn amain() void { |
| 6146 | fn amain() void { |
| 6160 | 6147 | seq('b'); |
| 6161 | 6148 | var f = async another(); |
| 6162 | 6149 | seq('e'); |
| 6163 | 6150 | final_result = await f; |
| 6164 | 6151 | seq('h'); |
| 6165 | 6152 | } |
| 6166 | | async fn another() i32 { |
| 6153 | fn another() i32 { |
| 6167 | 6154 | seq('c'); |
| 6168 | 6155 | suspend { |
| 6169 | 6156 | seq('d'); |
| ... | ... | @@ -6183,31 +6170,156 @@ fn seq(c: u8) void { |
| 6183 | 6170 | {#code_end#} |
| 6184 | 6171 | <p> |
| 6185 | 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 |
| 6187 | | implementations will make use of {#syntax#}suspend{#endsyntax#} internally. |
| 6173 | code will use only {#syntax#}async{#endsyntax#} and {#syntax#}await{#endsyntax#}, but event loop |
| 6174 | implementations will make use of {#syntax#}suspend{#endsyntax#} internally. |
| 6188 | 6175 | </p> |
| 6189 | 6176 | {#header_close#} |
| 6190 | | {#header_open|Open Issues#} |
| 6177 | |
| 6178 | {#header_open|Async Function Example#} |
| 6191 | 6179 | <p> |
| 6192 | | There are a few issues with async function that are considered unresolved. Best be aware of them, |
| 6193 | | as the situation is likely to change before 1.0.0: |
| 6180 | Putting all of this together, here is an example of typical |
| 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 | 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 | 6323 | {#header_close#} |
| 6212 | 6324 | |
| 6213 | 6325 | {#header_close#} |
| ... | ... | @@ -6265,6 +6377,49 @@ comptime { |
| 6265 | 6377 | Note: This function is deprecated. Use {#link|@typeInfo#} instead. |
| 6266 | 6378 | </p> |
| 6267 | 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 | 6423 | {#header_open|@atomicLoad#} |
| 6269 | 6424 | <pre>{#syntax#}@atomicLoad(comptime T: type, ptr: *const T, comptime ordering: builtin.AtomicOrder) T{#endsyntax#}</pre> |
| 6270 | 6425 | <p> |
| ... | ... | @@ -6855,6 +7010,44 @@ export fn @"A function name that is a complete sentence."() void {} |
| 6855 | 7010 | {#see_also|@intToFloat#} |
| 6856 | 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 | 7051 | {#header_open|@frameAddress#} |
| 6859 | 7052 | <pre>{#syntax#}@frameAddress() usize{#endsyntax#}</pre> |
| 6860 | 7053 | <p> |
| ... | ... | @@ -6870,14 +7063,14 @@ export fn @"A function name that is a complete sentence."() void {} |
| 6870 | 7063 | </p> |
| 6871 | 7064 | {#header_close#} |
| 6872 | 7065 | |
| 6873 | | {#header_open|@handle#} |
| 6874 | | <pre>{#syntax#}@handle(){#endsyntax#}</pre> |
| 7066 | {#header_open|@frameSize#} |
| 7067 | <pre>{#syntax#}@frameSize() usize{#endsyntax#}</pre> |
| 6875 | 7068 | <p> |
| 6876 | | This function returns a {#syntax#}promise->T{#endsyntax#} type, where {#syntax#}T{#endsyntax#} |
| 6877 | | is the return type of the async function in scope. |
| 7069 | This is the same as {#syntax#}@sizeOf(@Frame(func)){#endsyntax#}, where {#syntax#}func{#endsyntax#} |
| 7070 | may be runtime-known. |
| 6878 | 7071 | </p> |
| 6879 | 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 | 7074 | </p> |
| 6882 | 7075 | {#header_close#} |
| 6883 | 7076 | |