Async Frames, Suspend Blocks
@Frame(function)
returns the frame type of the function. This works for async
functions, and functions without a specific calling convention.
fn add(a: i32, b: i32) i64 {
return a + b;
}
test "@Frame" {
var frame: @Frame(add) = async add(1, 2);
try expect(await frame == 3);
}
@frame()
returns a pointer
to the frame of the current function. Similar to suspend
points, if this call
is found in a function then it is inferred as being async. All pointers to
frames coerce to the special type anyframe
, which you can use resume
upon.
This allows us to, for example, write a function that resumes itself.
fn double(value: u8) u9 {
suspend {
resume @frame();
}
return value * 2;
}
test "@frame 1" {
var f = async double(1);
try expect(nosuspend await f == 2);
}
Or, more interestingly, we can use it to tell other functions to resume us. Here we're introducing suspend blocks. Upon entering a suspend block, the async function is already considered suspended (i.e. it can be resumed). This means that we can have our function resumed by something other than the last resumer.
const std = @import("std");
fn callLater(comptime laterFn: fn () void, ms: u64) void {
suspend {
wakeupLater(@frame(), ms);
}
laterFn();
}
fn wakeupLater(frame: anyframe, ms: u64) void {
std.time.sleep(ms * std.time.ns_per_ms);
resume frame;
}
fn alarm() void {
std.debug.print("Time's Up!\n", .{});
}
test "@frame 2" {
nosuspend callLater(alarm, 1000);
}
Using the anyframe
data type can be thought of as a kind of type erasure, in
that we are no longer sure of the concrete type of the function or the function
frame. This is useful as it still allows us to resume the frame - in a lot of
code we will not care about the details and will just want to resume it. This
gives us a single concrete type which we can use for our async logic.
The natural drawback of anyframe
is that we have lost type information, and we
no longer know what the return type of the function is. This means we cannot
await an anyframe
. Zig's solution to this is the anyframe->T
types, where
the T
is the return type of the frame.
fn zero(comptime x: anytype) x {
return 0;
}
fn awaiter(x: anyframe->f32) f32 {
return nosuspend await x;
}
test "anyframe->T" {
var frame = async zero(f32);
try expect(awaiter(&frame) == 0);
}