Skip to content

Async & Await (Non-Blocking I/O)

Flame provides an asynchronous programming model designed for non-blocking I/O operations—such as network requests, file access, and socket streams—powered under the hood by Rust’s multi-threaded Tokio runtime.


Flame integrates asynchronous non-blocking I/O with a multi-threaded task scheduler:

  • Non-Blocking Tasks: Asynchronous tasks yield control during waiting periods (such as awaiting a remote HTTP response or socket readiness), allowing the underlying Tokio worker threads to process other tasks concurrently.
  • Multi-Threaded Runtime: Tasks are scheduled across Tokio’s multi-threaded worker pool, distributing I/O event processing across available CPU cores.
  • Prefix await: Unlike Rust’s postfix .await, Flame places await as a prefix operator before the expression (similar to JavaScript, Python, or C#).

When you define an async fn, it returns a lazy future (represented in Flame as a Promise):

async fn fetch_user_data(user_id: Int) -> String {
let response = await http.get("https://api.example.com/users/" + str(user_id))
return response.body
}
  1. Task Suspension: When an asynchronous operation is awaited, the task yields execution without blocking the underlying OS thread.
  2. Worker Scheduling: While the task is awaiting I/O readiness, the runtime worker thread is free to drive other tasks forward.
  3. Resumption: Once the awaited future completes or data arrives, the scheduler reactively resumes the task immediately following the await boundary.

In Flame, whenever you call an async fn or an asynchronous network module (like http.get), it does not immediately return the result. Instead, it returns a Promise.

A Promise (similar to a Future in Rust) is a handle representing a value that will become available once the background non-blocking operation completes.

  • You cannot access data properties directly from an unawaited Promise.
  • The concrete value is extracted by prefixing the promise expression with the await keyword.

In Flame, futures are lazy—no I/O is dispatched until evaluated with await:

// ❌ INCORRECT: Missing await does NOT send HTTP packets!
let res = http.get("https://api.example.com/data")
// res is an unresolved Future, not a Response struct!
// ✅ CORRECT:
let res = await http.get("https://api.example.com/data")
print($"Status: {res.status_code}")
import native.server
// ✅ Await socket binding so runtime engages persistent daemon lock:
let app = await server.init()
app.get("/status", () {
return { status: "Online" }
})
await app.listen(8080)

Separating I/O (async) from Compute (thread)

Section titled “Separating I/O (async) from Compute (thread)”
app.get("/compute", async (req) {
// Offload CPU math to a separate OS compute thread
let task = thread {
let mut sum = 0
let mut i = 0
while i < 100000000 {
sum = sum + i
i = i + 1
}
return sum
}
// Await thread completion without starving Tokio network workers!
let total = await task
return { status: "Done", sum: total }
})