Async / Await
Why This Matters
Async lets you represent latency/concurrency work with explicit types.
In Arden, Task<T> keeps async state visible in signatures, so callers know what must be awaited.
Core Model
async functionreturnsTask<T>awaitconvertsTask<T>intoTasync { ... }creates inline task expressions
If you are new to async:
Task<T>means "result will exist later"awaitmeans "pause here until that result is ready"- without
await, you are still holding deferred work, not final data
Basic Usage
async function fetchData(): Task<String> {
return "Data";
}
async function load(): Task<String> {
value: String = await fetchData();
return value;
}
Async Blocks
async function mainAsync(): Task<None> {
task: Task<Integer> = async {
return 21 * 2;
};
result: Integer = await task;
return None;
}
Task Methods
task.is_done(): Booleantask.cancel(): Nonetask.await_timeout(ms: Integer): Option<T>
When To Use Which
- normal code path:
await task - polling loop / non-blocking checks:
task.is_done() - bounded wait with fallback:
task.await_timeout(ms) - cooperative stop request:
task.cancel()
await_timeout rules enforced by compiler:
- argument must be
Integer - negative compile-time constants are rejected
Runtime Behavior Guidance
awaitis the normal completion pathis_done()is for polling-style checksawait_timeout(...)is for bounded waiting with fallback logiccancel()requests task cancellation; design code so cancellation is safe/idempotent where possible
Runnable Timeout Pattern
import std.io.*;
import std.time.*;
function slow(): Task<Integer> {
return async {
Time.sleep(200);
return 7;
};
}
function main(): None {
maybe: Option<Integer> = slow().await_timeout(50);
if (maybe.is_some()) {
println("completed: {maybe.unwrap()}");
} else {
println("timed out");
}
return None;
}
This pattern is the safest default for latency-sensitive code: always handle both
Some(value) and None branches explicitly.
Borrowing Interaction
Captures inside async blocks participate in borrow-check rules. Invalid moves/mutations after borrowed capture are rejected at compile time.
Async Borrow Boundary Rules (Important)
Current compiler boundary rules:
- async function parameters cannot contain borrowed references (
&T,&mut T, or nested borrowed-reference-bearing types) - async function return value cannot contain borrowed references across async boundary
- async blocks cannot capture bindings whose types contain borrowed references
Typical invalid patterns:
// invalid
// async function bad(x: &String): Task<Integer> { return 1; }
// invalid
// s: String = "x";
// r: &String = &s;
// t: Task<Integer> = async { return Str.len(*r); };
Safe default: convert to owned async boundaries. Do borrowed work synchronously, then pass/move owned values into async functions/blocks.
Common Mistakes
- forgetting that async APIs return
Task<T>(notT) - using wrong timeout type for
await_timeout - writing long async flows without explicit timeout or cancellation strategy
- attempting to pass/capture borrowed references across async boundaries