mirror of
https://github.com/ziglang/zig.git
synced 2026-02-13 21:08:36 +00:00
basic docs for new async/await semantics
This commit is contained in:
parent
55f5cee86b
commit
d3672493cc
@ -5970,54 +5970,25 @@ test "global assembly" {
|
||||
{#header_close#}
|
||||
{#header_open|Async Functions#}
|
||||
<p>
|
||||
When a function is called, a frame is pushed to the stack,
|
||||
the function runs until it reaches a return statement, and then the frame is popped from the stack.
|
||||
At the callsite, the following code does not run until the function returns.
|
||||
</p>
|
||||
<p>
|
||||
An async function is a function whose callsite is split into an {#syntax#}async{#endsyntax#} initiation,
|
||||
followed by an {#syntax#}await{#endsyntax#} completion.
|
||||
followed by an {#syntax#}await{#endsyntax#} completion. Its frame is
|
||||
provided explicitly by the caller, and it can be suspended and resumed any number of times.
|
||||
</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.
|
||||
Zig infers that a function is {#syntax#}async{#endsyntax#} when it observes that the function contains
|
||||
a <strong>suspension point</strong>. Async functions can be called the same as normal functions. A
|
||||
function call of an async function is a suspend point.
|
||||
</p>
|
||||
{#header_open|Suspend and Resume#}
|
||||
<p>
|
||||
An async function is like a function, but it can be suspended
|
||||
and resumed any number of times, and then it must be
|
||||
explicitly destroyed. When an async function suspends, it
|
||||
returns to the resumer.
|
||||
</p>
|
||||
{#header_open|Minimal Async Function Example#}
|
||||
<p>
|
||||
Declare an async function with the {#syntax#}async{#endsyntax#} keyword.
|
||||
The expression in angle brackets must evaluate to a struct
|
||||
which has these fields:
|
||||
</p>
|
||||
<ul>
|
||||
<li>{#syntax#}allocFn: fn (self: *Allocator, byte_count: usize, alignment: u29) Error![]u8{#endsyntax#} - where {#syntax#}Error{#endsyntax#} can be any error set.</li>
|
||||
<li>{#syntax#}freeFn: fn (self: *Allocator, old_mem: []u8) void{#endsyntax#}</li>
|
||||
</ul>
|
||||
<p>
|
||||
You may notice that this corresponds to the {#syntax#}std.mem.Allocator{#endsyntax#} 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 an async function with the {#syntax#}async{#endsyntax#} keyword. Here, the expression in angle brackets
|
||||
is a pointer to the allocator struct that the async function expects.
|
||||
</p>
|
||||
<p>
|
||||
The result of an async function call is a {#syntax#}promise->T{#endsyntax#} type, where {#syntax#}T{#endsyntax#}
|
||||
is the return type of the async function. Once a promise has been created, it must be
|
||||
consumed with {#syntax#}await{#endsyntax#}:
|
||||
</p>
|
||||
<p>
|
||||
Async functions start executing when created, so in the following example, the entire
|
||||
TODO
|
||||
At any point, a function may suspend itself. This causes control flow to
|
||||
return to the callsite (in the case of the first suspension),
|
||||
or resumer (in the case of subsequent suspensions).
|
||||
</p>
|
||||
{#code_begin|test#}
|
||||
const std = @import("std");
|
||||
@ -6025,32 +5996,25 @@ const assert = std.debug.assert;
|
||||
|
||||
var x: i32 = 1;
|
||||
|
||||
test "call an async function" {
|
||||
var frame = async simpleAsyncFn();
|
||||
comptime assert(@typeOf(frame) == @Frame(simpleAsyncFn));
|
||||
test "suspend with no resume" {
|
||||
var frame = async func();
|
||||
assert(x == 2);
|
||||
}
|
||||
fn simpleAsyncFn() void {
|
||||
|
||||
fn func() void {
|
||||
x += 1;
|
||||
suspend;
|
||||
// This line is never reached because the suspend has no matching resume.
|
||||
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>
|
||||
<p>
|
||||
TODO another test example here
|
||||
</p>
|
||||
<p>
|
||||
When an async function suspends itself, it must be sure that it will be
|
||||
resumed somehow, for example by registering its promise handle
|
||||
in an event loop. Use a suspend capture block to gain access to the
|
||||
promise (TODO this is outdated):
|
||||
In the same way that each allocation should have a corresponding free,
|
||||
Each {#syntax#}suspend{#endsyntax#} should have a corresponding {#syntax#}resume{#endsyntax#}.
|
||||
A <strong>suspend block</strong> allows a function to put a pointer to its own
|
||||
frame somewhere, for example into an event loop, even if that action will perform a
|
||||
{#syntax#}resume{#endsyntax#} operation on a different thread.
|
||||
{#link|@frame#} provides access to the async function frame pointer.
|
||||
</p>
|
||||
{#code_begin|test#}
|
||||
const std = @import("std");
|
||||
@ -6061,9 +6025,9 @@ var result = false;
|
||||
|
||||
test "async function suspend with block" {
|
||||
_ = async testSuspendBlock();
|
||||
std.debug.assert(!result);
|
||||
assert(!result);
|
||||
resume the_frame;
|
||||
std.debug.assert(result);
|
||||
assert(result);
|
||||
}
|
||||
|
||||
fn testSuspendBlock() void {
|
||||
@ -6075,19 +6039,15 @@ fn testSuspendBlock() void {
|
||||
}
|
||||
{#code_end#}
|
||||
<p>
|
||||
Every suspend point in an async function represents a point at which the async function
|
||||
could be destroyed. If that happens, {#syntax#}defer{#endsyntax#} expressions that are in
|
||||
scope are run, as well as {#syntax#}errdefer{#endsyntax#} expressions.
|
||||
</p>
|
||||
<p>
|
||||
{#link|Await#} counts as a suspend point.
|
||||
{#syntax#}suspend{#endsyntax#} causes a function to be {#syntax#}async{#endsyntax#}.
|
||||
</p>
|
||||
|
||||
{#header_open|Resuming from Suspend Blocks#}
|
||||
<p>
|
||||
Upon entering a {#syntax#}suspend{#endsyntax#} block, the async function is already considered
|
||||
suspended, and can be resumed. For example, if you started another kernel thread,
|
||||
and had that thread call {#syntax#}resume{#endsyntax#} on the promise handle provided by the
|
||||
{#syntax#}suspend{#endsyntax#} block, the new thread would begin executing after the suspend
|
||||
and had that thread call {#syntax#}resume{#endsyntax#} on the frame pointer provided by the
|
||||
{#link|@frame#}, the new thread would begin executing after the suspend
|
||||
block, while the old thread continued executing the suspend block.
|
||||
</p>
|
||||
<p>
|
||||
@ -6103,7 +6063,7 @@ test "resume from suspend" {
|
||||
_ = async testResumeFromSuspend(&my_result);
|
||||
std.debug.assert(my_result == 2);
|
||||
}
|
||||
async fn testResumeFromSuspend(my_result: *i32) void {
|
||||
fn testResumeFromSuspend(my_result: *i32) void {
|
||||
suspend {
|
||||
resume @frame();
|
||||
}
|
||||
@ -6113,32 +6073,59 @@ async fn testResumeFromSuspend(my_result: *i32) void {
|
||||
}
|
||||
{#code_end#}
|
||||
<p>
|
||||
This is guaranteed to be a tail call, and therefore will not cause a new stack frame.
|
||||
This is guaranteed to tail call, and therefore will not cause a new stack frame.
|
||||
</p>
|
||||
{#header_close#}
|
||||
{#header_close#}
|
||||
{#header_open|Await#}
|
||||
|
||||
{#header_open|Async and Await#}
|
||||
<p>
|
||||
In the same way that every {#syntax#}suspend{#endsyntax#} has a matching
|
||||
{#syntax#}resume{#endsyntax#}, every {#syntax#}async{#endsyntax#} has a matching {#syntax#}await{#endsyntax#}.
|
||||
</p>
|
||||
{#code_begin|test#}
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
test "async and await" {
|
||||
// Here we have an exception where we do not match an async
|
||||
// with an await. The test block is not async and so cannot
|
||||
// have a suspend point in it.
|
||||
// This is well-defined behavior, and everything is OK here.
|
||||
// Note however that there would be no way to collect the
|
||||
// return value of amain, if it were something other than void.
|
||||
_ = async amain();
|
||||
}
|
||||
|
||||
fn amain() void {
|
||||
var frame = async func();
|
||||
comptime assert(@typeOf(frame) == @Frame(func));
|
||||
|
||||
const ptr: anyframe->void = &frame;
|
||||
const any_ptr: anyframe = ptr;
|
||||
|
||||
resume any_ptr;
|
||||
await ptr;
|
||||
}
|
||||
|
||||
fn func() void {
|
||||
suspend;
|
||||
}
|
||||
{#code_end#}
|
||||
<p>
|
||||
The {#syntax#}await{#endsyntax#} keyword is used to coordinate with an async function's
|
||||
{#syntax#}return{#endsyntax#} statement.
|
||||
{#syntax#}return{#endsyntax#} statement.
|
||||
</p>
|
||||
<p>
|
||||
{#syntax#}await{#endsyntax#} is valid only in an {#syntax#}async{#endsyntax#} function, and it takes
|
||||
as an operand a promise handle.
|
||||
If the async function associated with the promise handle has already returned,
|
||||
then {#syntax#}await{#endsyntax#} destroys the target async function, and gives the return value.
|
||||
Otherwise, {#syntax#}await{#endsyntax#} suspends the current async function, registering its
|
||||
promise handle with the target async function. It becomes the target async function's responsibility
|
||||
to have ensured that it will be resumed or destroyed. When the target async function reaches
|
||||
its return statement, it gives the return value to the awaiter, destroys itself, and then
|
||||
resumes the awaiter.
|
||||
{#syntax#}await{#endsyntax#} is a suspend point, and takes as an operand anything that
|
||||
implicitly casts to {#syntax#}anyframe->T{#endsyntax#}.
|
||||
</p>
|
||||
<p>
|
||||
A frame handle must be consumed exactly once after it is created with {#syntax#}await{#endsyntax#}.
|
||||
</p>
|
||||
<p>
|
||||
{#syntax#}await{#endsyntax#} counts as a suspend point, and therefore at every {#syntax#}await{#endsyntax#},
|
||||
a async function can be potentially destroyed, which would run {#syntax#}defer{#endsyntax#} and {#syntax#}errdefer{#endsyntax#} expressions.
|
||||
There is a common misconception that {#syntax#}await{#endsyntax#} resumes the target function.
|
||||
It is the other way around: it suspends until the target function completes.
|
||||
In the event that the target function has already completed, {#syntax#}await{#endsyntax#}
|
||||
does not suspend; instead it copies the
|
||||
return value directly from the target function's frame.
|
||||
</p>
|
||||
{#code_begin|test#}
|
||||
const std = @import("std");
|
||||
@ -6156,14 +6143,14 @@ test "async function await" {
|
||||
assert(final_result == 1234);
|
||||
assert(std.mem.eql(u8, seq_points, "abcdefghi"));
|
||||
}
|
||||
async fn amain() void {
|
||||
fn amain() void {
|
||||
seq('b');
|
||||
var f = async another();
|
||||
seq('e');
|
||||
final_result = await f;
|
||||
seq('h');
|
||||
}
|
||||
async fn another() i32 {
|
||||
fn another() i32 {
|
||||
seq('c');
|
||||
suspend {
|
||||
seq('d');
|
||||
@ -6183,31 +6170,156 @@ fn seq(c: u8) void {
|
||||
{#code_end#}
|
||||
<p>
|
||||
In general, {#syntax#}suspend{#endsyntax#} is lower level than {#syntax#}await{#endsyntax#}. Most application
|
||||
code will use only {#syntax#}async{#endsyntax#} and {#syntax#}await{#endsyntax#}, but event loop
|
||||
implementations will make use of {#syntax#}suspend{#endsyntax#} internally.
|
||||
code will use only {#syntax#}async{#endsyntax#} and {#syntax#}await{#endsyntax#}, but event loop
|
||||
implementations will make use of {#syntax#}suspend{#endsyntax#} internally.
|
||||
</p>
|
||||
{#header_close#}
|
||||
{#header_open|Open Issues#}
|
||||
|
||||
{#header_open|Async Function Example#}
|
||||
<p>
|
||||
There are a few issues with async function that are considered unresolved. Best be aware of them,
|
||||
as the situation is likely to change before 1.0.0:
|
||||
Putting all of this together, here is an example of typical
|
||||
{#syntax#}async{#endsyntax#}/{#syntax#}await{#endsyntax#} usage:
|
||||
</p>
|
||||
{#code_begin|exe|async#}
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
pub fn main() void {
|
||||
_ = async amainWrap();
|
||||
|
||||
// Typically we would use an event loop to manage resuming async functions,
|
||||
// but in this example we hard code what the event loop would do,
|
||||
// to make things deterministic.
|
||||
resume global_file_frame;
|
||||
resume global_download_frame;
|
||||
}
|
||||
|
||||
fn amainWrap() void {
|
||||
amain() catch |e| {
|
||||
std.debug.warn("{}\n", e);
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
}
|
||||
std.process.exit(1);
|
||||
};
|
||||
}
|
||||
|
||||
fn amain() !void {
|
||||
const allocator = std.heap.direct_allocator;
|
||||
var download_frame = async fetchUrl(allocator, "https://example.com/");
|
||||
var awaited_download_frame = false;
|
||||
errdefer if (!awaited_download_frame) {
|
||||
if (await download_frame) |r| allocator.free(r) else |_| {}
|
||||
};
|
||||
|
||||
var file_frame = async readFile(allocator, "something.txt");
|
||||
var awaited_file_frame = false;
|
||||
errdefer if (!awaited_file_frame) {
|
||||
if (await file_frame) |r| allocator.free(r) else |_| {}
|
||||
};
|
||||
|
||||
awaited_file_frame = true;
|
||||
const file_text = try await file_frame;
|
||||
defer allocator.free(file_text);
|
||||
|
||||
awaited_download_frame = true;
|
||||
const download_text = try await download_frame;
|
||||
defer allocator.free(download_text);
|
||||
|
||||
std.debug.warn("download_text: {}\n", download_text);
|
||||
std.debug.warn("file_text: {}\n", file_text);
|
||||
}
|
||||
|
||||
var global_download_frame: anyframe = undefined;
|
||||
fn fetchUrl(allocator: *Allocator, url: []const u8) ![]u8 {
|
||||
const result = try std.mem.dupe(allocator, u8, "this is the downloaded url contents");
|
||||
errdefer allocator.free(result);
|
||||
suspend {
|
||||
global_download_frame = @frame();
|
||||
}
|
||||
std.debug.warn("fetchUrl returning\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
var global_file_frame: anyframe = undefined;
|
||||
fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 {
|
||||
const result = try std.mem.dupe(allocator, u8, "this is the file contents");
|
||||
errdefer allocator.free(result);
|
||||
suspend {
|
||||
global_file_frame = @frame();
|
||||
}
|
||||
std.debug.warn("readFile returning\n");
|
||||
return result;
|
||||
}
|
||||
{#code_end#}
|
||||
<p>
|
||||
Now we remove the {#syntax#}suspend{#endsyntax#} and {#syntax#}resume{#endsyntax#} code, and
|
||||
observe the same behavior, with one tiny difference:
|
||||
</p>
|
||||
{#code_begin|exe|blocking#}
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
pub fn main() void {
|
||||
_ = async amainWrap();
|
||||
}
|
||||
|
||||
fn amainWrap() void {
|
||||
amain() catch |e| {
|
||||
std.debug.warn("{}\n", e);
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
}
|
||||
std.process.exit(1);
|
||||
};
|
||||
}
|
||||
|
||||
fn amain() !void {
|
||||
const allocator = std.heap.direct_allocator;
|
||||
var download_frame = async fetchUrl(allocator, "https://example.com/");
|
||||
var awaited_download_frame = false;
|
||||
errdefer if (!awaited_download_frame) {
|
||||
if (await download_frame) |r| allocator.free(r) else |_| {}
|
||||
};
|
||||
|
||||
var file_frame = async readFile(allocator, "something.txt");
|
||||
var awaited_file_frame = false;
|
||||
errdefer if (!awaited_file_frame) {
|
||||
if (await file_frame) |r| allocator.free(r) else |_| {}
|
||||
};
|
||||
|
||||
awaited_file_frame = true;
|
||||
const file_text = try await file_frame;
|
||||
defer allocator.free(file_text);
|
||||
|
||||
awaited_download_frame = true;
|
||||
const download_text = try await download_frame;
|
||||
defer allocator.free(download_text);
|
||||
|
||||
std.debug.warn("download_text: {}\n", download_text);
|
||||
std.debug.warn("file_text: {}\n", file_text);
|
||||
}
|
||||
|
||||
fn fetchUrl(allocator: *Allocator, url: []const u8) ![]u8 {
|
||||
const result = try std.mem.dupe(allocator, u8, "this is the downloaded url contents");
|
||||
errdefer allocator.free(result);
|
||||
std.debug.warn("fetchUrl returning\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 {
|
||||
const result = try std.mem.dupe(allocator, u8, "this is the file contents");
|
||||
errdefer allocator.free(result);
|
||||
std.debug.warn("readFile returning\n");
|
||||
return result;
|
||||
}
|
||||
{#code_end#}
|
||||
<p>
|
||||
Previously, the {#syntax#}fetchUrl{#endsyntax#} and {#syntax#}readFile{#endsyntax#} functions suspended,
|
||||
and were resumed in an order determined by the {#syntax#}main{#endsyntax#} function. Now,
|
||||
since there are no suspend points, the order of the printed "... returning" messages
|
||||
is determined by the order of {#syntax#}async{#endsyntax#} callsites.
|
||||
</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
|
||||
async function. 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#}
|
||||
@ -6265,6 +6377,49 @@ comptime {
|
||||
Note: This function is deprecated. Use {#link|@typeInfo#} instead.
|
||||
</p>
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@asyncCall#}
|
||||
<pre>{#syntax#}@asyncCall(frame_buffer: []u8, result_ptr, function_ptr, args: ...) anyframe->T{#endsyntax#}</pre>
|
||||
<p>
|
||||
{#syntax#}@asyncCall{#endsyntax#} performs an {#syntax#}async{#endsyntax#} call on a function pointer,
|
||||
which may or may not be an {#link|async function|Async Functions#}.
|
||||
</p>
|
||||
<p>
|
||||
The provided {#syntax#}frame_buffer{#endsyntax#} must be large enough to fit the entire function frame.
|
||||
This size can be determined with {#link|@frameSize#}. To provide a too-small buffer
|
||||
invokes safety-checked {#link|Undefined Behavior#}.
|
||||
</p>
|
||||
<p>
|
||||
{#syntax#}result_ptr{#endsyntax#} is optional ({#link|null#} may be provided). If provided,
|
||||
the function call will write its result directly to the result pointer, which will be available to
|
||||
read after {#link|await|Async and Await#} completes. Any result location provided to
|
||||
{#syntax#}await{#endsyntax#} will copy the result from {#syntax#}result_ptr{#endsyntax#}.
|
||||
</p>
|
||||
{#code_begin|test#}
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
test "async fn pointer in a struct field" {
|
||||
var data: i32 = 1;
|
||||
const Foo = struct {
|
||||
bar: async fn (*i32) void,
|
||||
};
|
||||
var foo = Foo{ .bar = func };
|
||||
var bytes: [64]u8 = undefined;
|
||||
const f = @asyncCall(&bytes, {}, foo.bar, &data);
|
||||
assert(data == 2);
|
||||
resume f;
|
||||
assert(data == 4);
|
||||
}
|
||||
|
||||
async fn func(y: *i32) void {
|
||||
defer y.* += 2;
|
||||
y.* += 1;
|
||||
suspend;
|
||||
}
|
||||
{#code_end#}
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@atomicLoad#}
|
||||
<pre>{#syntax#}@atomicLoad(comptime T: type, ptr: *const T, comptime ordering: builtin.AtomicOrder) T{#endsyntax#}</pre>
|
||||
<p>
|
||||
@ -6855,6 +7010,44 @@ export fn @"A function name that is a complete sentence."() void {}
|
||||
{#see_also|@intToFloat#}
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@frame#}
|
||||
<pre>{#syntax#}@frame() *@Frame(func){#endsyntax#}</pre>
|
||||
<p>
|
||||
This function returns a pointer to the frame for a given function. This type
|
||||
can be {#link|implicitly cast|Implicit Casts#} to {#syntax#}anyframe->T{#endsyntax#} and
|
||||
to {#syntax#}anyframe{#endsyntax#}, where {#syntax#}T{#endsyntax#} is the return type
|
||||
of the function in scope.
|
||||
</p>
|
||||
<p>
|
||||
This function does not mark a suspension point, but it does cause the function in scope
|
||||
to become an {#link|async function|Async Functions#}.
|
||||
</p>
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@Frame#}
|
||||
<pre>{#syntax#}@Frame(func: var) type{#endsyntax#}</pre>
|
||||
<p>
|
||||
This function returns the frame type of a function. This works for {#link|Async Functions#}
|
||||
as well as any function without a specific calling convention.
|
||||
</p>
|
||||
<p>
|
||||
This type is suitable to be used as the return type of {#link|async|Async and Await#} which
|
||||
allows one to, for example, heap-allocate an async function frame:
|
||||
</p>
|
||||
{#code_begin|test#}
|
||||
const std = @import("std");
|
||||
|
||||
test "heap allocated frame" {
|
||||
const frame = try std.heap.direct_allocator.create(@Frame(func));
|
||||
frame.* = async func();
|
||||
}
|
||||
|
||||
fn func() void {
|
||||
suspend;
|
||||
}
|
||||
{#code_end#}
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@frameAddress#}
|
||||
<pre>{#syntax#}@frameAddress() usize{#endsyntax#}</pre>
|
||||
<p>
|
||||
@ -6870,14 +7063,14 @@ export fn @"A function name that is a complete sentence."() void {}
|
||||
</p>
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@handle#}
|
||||
<pre>{#syntax#}@handle(){#endsyntax#}</pre>
|
||||
{#header_open|@frameSize#}
|
||||
<pre>{#syntax#}@frameSize() usize{#endsyntax#}</pre>
|
||||
<p>
|
||||
This function returns a {#syntax#}promise->T{#endsyntax#} type, where {#syntax#}T{#endsyntax#}
|
||||
is the return type of the async function in scope.
|
||||
This is the same as {#syntax#}@sizeOf(@Frame(func)){#endsyntax#}, where {#syntax#}func{#endsyntax#}
|
||||
may be runtime-known.
|
||||
</p>
|
||||
<p>
|
||||
This function is only valid within an async function scope.
|
||||
This function is typically used in conjunction with {#link|@asyncCall#}.
|
||||
</p>
|
||||
{#header_close#}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user