Async functions and tasks

An async function gives a Task at once and does its work while the program goes on; await waits for a task and gives its Result, which ? passes on as for any other:

import "std/async"

async func load_both(str a, str b) Result[str] do
    Task[str] first = fetch(a)          // both start now
    Task[str] second = fetch(b)
    str text = await first? + await second?
    return Result.OK(text)
end

match await load_both("a.txt", "b.txt") do
    when OK(text) then
        print(text)
    when ERROR(e) then
        eprint(e)
end

Calling an async function starts it: it runs until it awaits a task that has not settled, and then the caller goes on with the Task it was given. The task settles when the function returns, with what it returns, or when a ? in it passes an error on, which fails the task whatever the function returns:

Declared result Task Settles with
Result[T] Task[T] the value of OK, or the error
T Task[T] the value
nothing Task[bool] true

await task gives a Result[T]: OK with the value, or ERROR with the error the task failed with. It can be used in an async function's own statements, where the function stops until the task settles while other code runs, and in the program's body, which runs the event loop meanwhile; awaiting a task that nothing on the loop will settle is then a runtime error. await binds tighter than ?, so await load(path)? is the value. It cannot be used in a function written as an expression, in deferred code, or in a function that is not async.

A method can be async, as in async func save(self) Result[bool]; a function of a type that does not take self cannot. Async functions can be generic and pub, and are declared only at the top level or in a type.

Task[T] is a type that every program has. Task() makes one that is pending until something settles it, and done says whether it has; the module std/async settles tasks by hand and combines them, with timers, timeouts, and cancellation:

Task[str] answer = Task()
after(100, func() do
    resolve(answer, "ready")
end)
print(await answer?)

Each task's code runs on a stack of its own, so it can stop at an await in the middle of any calls and go on later: deferred code runs when the async function ends, a runtime error shows the calls that led to it through the tasks that awaited one another, and the collector sees the values of the tasks that wait. Waiting tasks go on as callbacks of the event loop, so a program that neither awaits nor runs the loop ends without them. Each stack has 1 MB, of which deeply recursive code in a task may use 896 KB before the stack guard stops it. On POSIX, async functions run on x86-64 and AArch64 processors.