# Chapter 21: Async on native

> ⚠ **Planned for native binaries — not in Loup 1.25.** The Language Reference (§2-1.1 to §2-1.9) specifies `async`/`await` for native programs, running on a multi-threaded, work-stealing executor. The 1.25.1 native code generator has no task scheduler and refuses any program that contains an `async` function. `async` does run on the interpreter (`loup run`) and in WebAssembly builds of `host.dual` programs, and the listings in this chapter were checked that way. The parts marked ⚠ below are not implemented on any path.

This chapter shows the async syntax as it works today, what a native build says when it meets it, and what the specification plans.

## What a native build does

A program with an `async` function builds for every target except `native`. The native build stops before generating code and names each function it cannot lower:

```sh
loup build --target native -o app
```

```text
error[E-BUILD-NATIVE]: unsupported in managed native (libc foundation): async function `fetchSize` (native builds have no task scheduler: `async`/`await` run on the interpreters only); async function `main` (native builds have no task scheduler: `async`/`await` run on the interpreters only); async function `total` (native builds have no task scheduler: `async`/`await` run on the interpreters only); …
```

So, for a program that needs `async` today:

- write it in `host.dual` (`.loup`), which is the mode meant for code delivered more than one way (chapter 2);
- run it with `loup run`, or build it as WebAssembly (`loup-bootstrap build`, then `loup-launcher --run-bare build/<name>.wasm`) or as a packaged app (`loup-bootstrap build --target desktop`, chapter 24);
- for parallel CPU work in a native binary, use threads (chapter 20) instead.

## `async` functions and `await`

An `async func` returns a `Task[T]`. `await` waits for a task and gives its value. `await` is allowed only inside an `async` function, and `main` may itself be `async`:

```loup
@mode(host.dual)

async func fetchSize(name: string): Task[int] {
    println("fetching " + name);
    return name.length() * 100;
}

async func total(): Task[int] {
    var page = 0;
    var script = 0;
    await group {
        page = spawn fetchSize("index.html");
        script = spawn fetchSize("app.js");
    }
    return page + script;
}

async func main(): void {
    println("start");
    let t = total();
    println("called");
    println(await t);
}
```

*Listing 21-1: `async` functions, `await`, and an `await group`*

```text
start
fetching index.html
fetching app.js
called
1600
```

`await group { … }` is the structured way to run several tasks: each `spawn` starts a child task, the block waits for all of them, and each `x = spawn f()` assigns the child's result to `x` when the group ends (§2-1.2). If a child fails, the group fails with its exception.

The listing uses `+` rather than `i"…"` interpolation because interpolated strings crash a WebAssembly build of 1.25.1 at run time; under `loup run` either works.

> **Known gap: async runs eagerly.** The output above shows it: `fetching …` is printed before `called`, because calling an `async` function runs its whole body, including everything it awaits, before the call returns. The interpreter and WebAssembly have no scheduler yet, so nothing interleaves, and an `await group` runs its children one after another in order. Results and exceptions are as specified; concurrency is not. Do not write code that depends on two tasks making progress at the same time.

### Mistake: `await` outside an `async` function

```loup,does_not_compile
@mode(host.dual)

async func fetchSize(name: string): Task[int] {
    return name.length() * 100;
}

func main(): void {
    println(await fetchSize("index.html"));
}
```

```text
main.loup:8:13: error[E_AWAIT_NOT_ASYNC]: E_AWAIT_NOT_ASYNC: `await` is only valid inside an `async func` body, an entry script's top level, or the REPL (§2-1.1)
```

Make the caller `async` too, here `async func main(): void`. Async-ness spreads up the call chain to `main` this way, which is why the specification keeps it out of code that does not need it.

### Mistake: an immutable `await group` target

```loup,does_not_compile
@mode(host.dual)

async func fetchSize(name: string): Task[int] {
    return name.length() * 100;
}

async func main(): void {
    let page: int = 0;
    await group {
        page = spawn fetchSize("index.html");
    }
    println(page);
}
```

```text
main.loup:10:9: error[E0001]: cannot assign to immutable binding 'page'
```

The group assigns the result when it ends, so the target must be a `var`, as in Listing 21-1.

## Errors through `await`

An exception thrown inside an `async` function is rethrown by the `await` that waits for it, so ordinary `try`/`catch` works across tasks:

```loup
#~ @mode(host.dual)
class BadInput : Exception {
    new(message: string) { super(message); }
}

async func parsePort(text: string): Task[int] {
    if (text == "") {
        throw BadInput("port is empty");
    }
    return text.length();
}

async func main(): void {
    try {
        let port = await parsePort("");
        println(port);
    } catch (BadInput as e) {
        println("failed: " + e.message);
    }
}
```

*Listing 21-2: Catching an async function's exception at the `await`*

```text
failed: port is empty
```

## Async generators and `for await`

An `async gen func` produces values one at a time and may `await` between them; `for await` consumes them. Its return type is `AsyncIterable[T]`:

```loup
#~ @mode(host.dual)
async gen func pages(count: int): AsyncIterable[int] {
    var n = 1;
    while (n <= count) {
        yield n;
        n = n + 1;
    }
}

async func main(): void {
    var total = 0;
    for await (p in pages(3)) {
        println("page " + str(p));
        total = total + p;
    }
    println(total);
}
```

*Listing 21-3: An async generator consumed with `for await`*

```text
page 1
page 2
page 3
6
```

Earlier editions spelled the return type `AsyncIterator[T]`, which is `undefined type 'AsyncIterator'`; the type is `AsyncIterable[T]` (§2-1.4). A WebAssembly build crashes on `for await` in 1.25.1, so code that uses it runs only under `loup run` for now.

## Blocking work: `spawnBlocking`

`spawnBlocking(body)` is a prelude function that runs a synchronous closure on a pool reserved for blocking work and returns a `Task` for its result (§2-1.9.3). Under the interpreter it runs the closure at once, like everything else in this chapter:

```loup
#~ @mode(host.dual)
func checksum(data: string): int {
    var h = 7;
    for (i in 0..data.length()) {
        h = (h * 31 + data.length()) % 1000003;
    }
    return h;
}

async func main(): void {
    let sum = await spawnBlocking(func(): int { return checksum("payload"); });
    println(sum);
}
```

*Listing 21-4: Offloading a synchronous computation with `spawnBlocking`*

```text
311715
```

Older material imports it from `sync.thread`; that module does not exist, and the prelude function needs no import.

## What is planned

These parts of the specification have no implementation on any delivery path in 1.25.1:

- **The native executor** (§2-1.9): `async` in native binaries, the work-stealing scheduler, and its configuration through `runtime.configure(RuntimeConfig(executor = ExecutorConfig(workerThreads = …)))`.
- **`TaskGroup`** (§2-1.3): `TaskGroup.collect`, `race`, `withTimeout`, `withCancellation` and `collectBounded`. The name is `undefined identifier 'TaskGroup'`. Use `await group { … }` for a fixed set of tasks.
- **Unstructured tasks and cancellation:** `Task` is a type, not a value with methods, so `Task.spawn`, `Task.all` and `t.cancel()` do not exist. `CancellationError` is not a built-in exception (`undefined identifier 'CancellationError'`).
- **Combinators:** there is no `concurrency.combinators` module (`all`, `race`, `timeout`, `retry`).
- **Actors** (`Actor[M]`, §2-1.6) and **task-local storage** (`TaskLocal[T]`, §2-1.9.8).
- **`@concurrent`** (§2-1.9.5) is accepted on an `async func`, but has nothing to check against until there is a multi-threaded executor.

A sketch of the planned structured form, for reference only:

```loup,ignore
@mode(host.native)

async func checkAll(urls: list[string]): Task[list[int]] {
    return await TaskGroup.withTimeout(5000, async func(): Task[list[int]] {
        return await TaskGroup.collect(urls, async func(url: string): Task[int] {
            return await probe(url);
        });
    });
}
```

## Async or threads?

Until the native executor lands, the choice in a native program is simple: use threads (chapter 20). In `host.dual` code that also has to run in WebAssembly, `async` is the portable way to express waiting, and its results and errors behave as specified; just do not expect two tasks to run at the same time.

## Summary

- `async func f(): Task[T]` declares a task-returning function; `await` is valid only in `async` functions (`E_AWAIT_NOT_ASYNC`), and `main` may be `async`.
- `await group { x = spawn f(); }` runs child tasks and assigns their results to `var` targets; `async gen func` with `AsyncIterable[T]` and `for await` stream values; exceptions propagate through `await`.
- Native builds refuse `async` (`E-BUILD-NATIVE`). Under `loup run` and in WebAssembly, async code runs eagerly and sequentially.
- `spawnBlocking` is a prelude builtin returning a `Task`.
- `TaskGroup`, `Task.spawn`, cancellation, combinators, actors and the multi-threaded executor are ⚠ planned.
- Chapter 22 previews the planned desktop UI library.
