Async & Await in Desi¶
Desi supports asynchronous programming with async def and await. Async functions run on background threads and return Future handles that you can await later.
Quick Start¶
async def add1(x: int) -> int:
return x + 1
def main() -> int:
let fut = add1(41) # Spawns on background thread, returns immediately
let n = await fut # Blocks until result is ready
print(str(n)) # → 42
return 0
Defining Async Functions¶
Add async before def to make a function asynchronous:
# Async function returning int
async def compute(x: int) -> int:
return x * x
# Async function returning str
async def greet(name: str) -> str:
return "Hello " + name
# Async function with multiple parameters
async def add(a: int, b: int) -> int:
return a + b
When you call an async function, it immediately returns a Future — the actual work runs on a background thread.
Awaiting Results¶
Use await to get the result from a Future:
let fut = compute(7) # Returns Future immediately
# ... do other work here ...
let result = await fut # Blocks until compute() finishes
print(str(result)) # → 49
⚠️ Important:
awaitblocks the current thread until the async function completes. The async function itself runs on a separate thread.
Parallel Execution¶
Launch multiple async functions and await them all:
async def double(x: int) -> int:
return x * 2
def main() -> int:
# Both run concurrently on separate threads
let f1 = double(10)
let f2 = double(20)
# Await both results
let a = await f1 # → 20
let b = await f2 # → 40
print(str(a + b)) # → 60
return 0
Both double(10) and double(20) start running immediately when called. The await calls collect the results.
Supported Return Types¶
Async functions work with all Desi types:
# Primitives
async def get_number() -> int:
return 42
async def get_flag() -> bool:
return true
async def get_name() -> str:
return "Desi"
# Collections and user-defined types work too
# (structs, classes, lists, dicts — anything that's a pointer type)
Complete Example¶
async def fetch_greeting(name: str) -> str:
return "Hello, " + name + "!"
async def fetch_count() -> int:
return 42
def main() -> int:
# Launch both async operations
let greeting_fut = fetch_greeting("Desi")
let count_fut = fetch_count()
# Await results
let greeting = await greeting_fut
let count = await count_fut
print(greeting) # → Hello, Desi!
print(str(count)) # → 42
return 0
How It Works¶
Under the hood, async def creates two functions:
- Wrapper: Creates a
Future, spawns the body on a background thread, returns theFuture - Body: Runs the actual function code on the background thread
main thread background thread
─────────── ─────────────────
fut = fetch_greeting("Desi")
└─ creates Future
└─ spawns body thread ──────► body("Desi") runs
└─ returns Future │
│ computes result
...do other work... │
▼
greeting = await fut ◄────────── future completes
Borrowing Rules¶
Desi enforces that inout (mutable borrow) parameters cannot be held across await points:
# ❌ COMPILE ERROR: inout borrow across await
async def bad(inout x: int) -> int:
let fut = some_async_call()
await fut # Error! inout 'x' held across await
return x
This prevents data races — if the original value could change while the async function is suspended, the borrow could become invalid.
Current Limitations¶
- No async closures/lambdas yet — only named functions can be
async - No async methods yet — class/struct methods cannot be
async - No cancellation — once spawned, an async function runs to completion
- Thread per call — each async call spawns a new pthread (no thread pool yet)
- Must await — if a
Futureis never awaited, the thread still runs but resources may leak