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 |