Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Async Functions

Async functions are functions that execute asynchronously and must be awaited to retrieve their result. Forge provides dual syntax: async fn (classic) and forge (natural).

Syntax

AsyncFnDef    = ("async" "fn" | "forge") Identifier "(" ParamList ")" Block

Defining Async Functions

Classic syntax:

async fn fetch_data() {
    let resp = http.get("https://api.example.com/data")
    return resp
}

Natural syntax:

forge fetch_data() {
    let resp = http.get("https://api.example.com/data")
    return resp
}

Both forms are semantically identical. The parser produces the same FnDef AST node with is_async: true.

Semantics

An async function is defined with the is_async flag set to true in the AST. In the current implementation, async functions are stored as regular function values. The is_async flag is recorded in the AST but the interpreter treats async functions identically to synchronous functions during definition.

The distinction becomes relevant at the call site: async functions are expected to be called with await (or hold) to retrieve their result. Without await, the function executes synchronously and its return value is available immediately.

Registration

When an async function definition is executed, the function is registered in the environment as a Value::Function just like a synchronous function. The is_async flag from the AST does not alter the stored function value.

forge get_value() {
    return 42
}

// The function is callable like any other function
let result = get_value()       // 42 (runs synchronously)
let result = await get_value() // 42 (await passes through non-handle values)

Parameters and Return Values

Async functions support the same parameter syntax as regular functions, including default values and type annotations:

async fn fetch_user(id: Int, timeout: Int = 30) {
    let resp = http.get("https://api.example.com/users/" + str(id))
    return json.parse(resp.body)
}

Return values follow the same rules as synchronous functions. The return statement provides an explicit return value; without it, the function returns null or the last expression’s value.

Combining with Spawn

For true concurrent execution, combine async functions with spawn:

forge compute(n) {
    // Expensive computation
    return n * n
}

let handle = spawn { return compute(42) }
let result = await handle    // 1764

The spawn keyword is what creates actual concurrency (a new OS thread). The async/forge keyword marks intent but does not itself create a new thread.

Natural Syntax: forge

The forge keyword serves double duty as both the language name and the natural-syntax alias for async fn. In a function definition context, forge is parsed as an async function definition:

forge load_config() {
    let text = fs.read("config.json")
    return json.parse(text)
}

This reads naturally as “forge a load_config function” while being functionally equivalent to async fn load_config().