Async & Concurrency

Zolo provides first-class support for asynchronous programming through async/await, and structured concurrency via spawn and every.

Async Functions #

Declare an async function with the async keyword:

async fn fetch_data(url: str) -> str {
    let response = await http.get(url)
    return response.body
}

Async functions return a promise/future that can be awaited.

Await #

Use await inside an async function to pause execution until a future resolves:

async fn greet_user(id: int) -> str {
    let user = await fetch_user(id)
    let profile = await fetch_profile(user.name)
    return "Hello, {user.name}! Bio: {profile.bio}"
}

await can be used on any expression that returns an async value:

async fn main() {
    let result = await greet_user(42)
    print(result)
}

Sequential vs Parallel Awaiting #

By default, multiple await calls run sequentially:

async fn sequential() {
    let a = await task_a()   // waits for A first
    let b = await task_b()   // then waits for B
}

To run tasks in parallel, use spawn:

async fn parallel() {
    let handle_a = spawn task_a()
    let handle_b = spawn task_b()
    let a = await handle_a
    let b = await handle_b
    print("Both done: {a}, {b}")
}

Spawn #

spawn launches a coroutine concurrently and returns a handle:

// Spawn an expression
let handle = spawn some_async_fn()

// Spawn a block
let handle2 = spawn {
    let data = fetch_data("https://api.example.com")
    process(data)
}

// Await the result later
let result = await handle
let result2 = await handle2

Fire-and-forget #

You can spawn without awaiting for background tasks:

spawn log_to_server("user_logged_in")
// continues without waiting

Every #

every schedules a block to run repeatedly at a given interval (in milliseconds):

// Run every 1000ms (1 second)
every 1000 {
    print("tick!")
}

// Run every 500ms with a dynamic interval
let interval = 500
every interval {
    check_health()
}

Combining with spawn #

// Background heartbeat
spawn {
    every 5000 {
        send_heartbeat()
    }
}

// Main work continues
let data = await fetch_data(url)
process(data)

Async Error Handling #

Async functions compose naturally with Result:

async fn safe_fetch(url: str) -> Result<str, str> {
    let response = await http.get(url)
    if response.status != 200 {
        return Result.Err("HTTP {response.status}")
    }
    return Result.Ok(response.body)
}

async fn main() {
    match await safe_fetch("https://api.example.com") {
        Result.Ok(body) => print("Got: {body}"),
        Result.Err(e)   => print("Error: {e}"),
    }
}

The ? operator also works in async functions:

async fn pipeline(url: str) -> Result<str, str> {
    let raw = await safe_fetch(url)?
    let parsed = parse_json(raw)?
    return Result.Ok(parsed.title)
}

Practical Pattern: Concurrent Fetch #

async fn fetch_all(urls: [str]) -> [str] {
    // Spawn all fetches concurrently
    let handles = urls |> Array.map(|url| spawn fetch_data(url))

    // Await all results
    return handles |> Array.map(|h| await h)
}

Key Points #

Concept Syntax Description
Async function async fn name() {} Returns a future
Await await expr Pauses until future resolves
Spawn expression spawn expr Run concurrently, returns handle
Spawn block spawn { ... } Run block concurrently
Repeating task every ms { ... } Execute block on interval
enespt-br