Ergonomic asynchronous programming in Turmeric using fibers and delimited continuations.
Turmeric's async/await syntax enables direct-style asynchronous programming for I/O-bound and concurrent tasks. The implementation builds on Phase 18's delimited continuations and integrates with Turmeric's effect system.
;; Define an async function
(async
(def data (await (read-file "data.txt")))
(def result (await (process-data data)))
(println result))
;; Type: returns a Future<T>
(def fut (async
(+ 1 (await (fetch 2)))))
;; Block until completion
(println (await fut)) ; => 3
A fiber is a user-space thread (lightweight thread) that: - Has its own execution state (implemented via delimited continuations) - Can yield (suspend) and resume - Runs on an OS thread managed by a scheduler - Has no separate OS stack (avoids thread creation overhead)
(async body) — Creates a fiber that executes body. Returns a Future<T> that can be awaited.(await fut) — Suspends the current fiber until fut completes. Used only inside async blocks.Future<T> — Represents a computation that may not be done yet.(await fut).awaits sequence operations.v1 (Phase 20): Single-threaded scheduler — all fibers run on one OS thread. Simpler; no data races.
v2 (Phase 21+): Multi-threaded scheduler — fibers run on a thread pool. Higher throughput.
| Decision | Rationale |
|---|---|
| Fiber-based model | Leverages delimited continuations; avoids C stack issues |
await desugars to shift |
Integrates with existing CPS infrastructure |
async desugars to reset |
Minimal new machinery; natural fit for the foundation |
Future<T> as return type |
Composable, can be awaited or passed to other code |
| No implicit thread spawning | Explicit control for predictable performance |
Integration with defer |
Cleanup on fiber completion; consistent with scope model |
;; Surface syntax
(async (+ 1 (await (fetch 2))))
;; Desugars to (conceptually)
(reset
(fn []
(+ 1 (shift k
(fiber-suspend (fetch 2) k)))))
;; The scheduler later resumes k with the result of (fetch 2)
| Feature | Threads (Phase 19) | Async/Await (Phase 20) |
|---|---|---|
| Model | OS-level 1:1 threads | User-space fibers |
| Overhead | ~10-100μs per thread | ~1μs per fiber |
| Scalability | 100s-1000s max | 100k+ feasible |
| Stack | Real OS stack | CPS-based (heap) |
| Use case | CPU-bound parallelism | I/O-bound concurrency |
(async
(def a (await (fetch-a)))
(def b (await (fetch-b)))
(process a b))
;; Fork two concurrent operations; wait for both
(async
(let [fut-a (async (fetch-a))
fut-b (async (fetch-b))]
(let [a (await fut-a)
b (await fut-b)]
(process a b))))
Effects-based try/catch works within async blocks (see Effects System Guide):
(async
(try-with
(fn []
(await (fetch-file "missing.txt")))
(fn [e k]
(match e
(FileNotFound _) -> (continue k "default")))))
Turmeric doesn't provide built-in async I/O primitives. Import via FFI:
;; Example: libuv or custom C bindings
(defn read-file [path]
(with-future-from-ffi "libuv_read" path))
(async
(println (await (read-file "data.txt"))))