This document describes the Asupersync macro DSL for structured concurrency:
scope!, spawn!, join!, join_all!, and race!.
The macros are designed to reduce boilerplate while preserving Asupersync invariants: structured concurrency, cancellation correctness, and deterministic testing.
Enable the proc-macros feature and import the macros you need.
[dependencies]
asupersync = { path = ".", features = ["proc-macros"] }use asupersync::proc_macros::{scope, spawn, join, join_all, race};This snippet is fully runnable today because it only uses join!.
use asupersync::proc_macros::join;
use asupersync::runtime::RuntimeBuilder;
fn main() {
let rt = RuntimeBuilder::current_thread()
.build()
.expect("runtime");
let (a, b) = rt.block_on(async {
join!(async { 1 }, async { 2 })
});
assert_eq!(a + b, 3);
}The macro DSL is usable in Phase 0, but some runtime wiring is still in progress. Keep the following in mind:
scope!currently callsCx::scope()which binds to the existing region (child regions will be added in later phases).spawn!requires a__state: &mut RuntimeStatevariable to exist in scope. This is pending fuller runtime integration.race!expands toCx::race*methods that are planned but not yet implemented.join!is sequential in Phase 0. Concurrency comes in later phases.
These are phase limitations, not permanent API choices.
Create a structured concurrency scope. The macro binds a scope variable inside
the body.
Syntax
scope!(cx, { ... })
scope!(cx, "name", { ... })
scope!(cx, budget: Budget::INFINITE, { ... })
scope!(cx, "name", budget: Budget::INFINITE, { ... })Expansion (conceptual)
{
let __cx = &cx;
let __scope = __cx.scope();
async move {
let scope = __scope;
/* body */
}.await
}Notes
scope!always inserts.await, so it must be invoked inside an async context.returnis rejected inside the body. Use early-return patterns instead.
Spawn work inside the current scope.
Syntax
spawn!(future)
spawn!("name", future)
spawn!(scope, future)
spawn!(scope, "name", future)Expansion (conceptual)
scope.spawn_registered(__state, __cx, |cx| async move { future.await })Notes
spawn!expects__state: &mut RuntimeStateand__cx: &Cxto be in scope.- The handle is returned immediately; scheduling is handled by the runtime.
Join multiple futures and return a tuple of results.
Syntax
join!(f1, f2, f3)
join!(cx; f1, f2, f3)Notes
- Phase 0: sequential awaits (still correct, just not parallel).
cx;is reserved for future cancellation propagation.
Join multiple futures and return an array.
Syntax
join_all!(f1, f2, f3)Notes
- All futures must return the same type.
- Useful when you want to iterate results.
Race futures and return the first completion. Losers are cancelled and drained.
Syntax
race!(cx, { f1, f2 })
race!(cx, { "fast" => f1, "slow" => f2 })
race!(cx, timeout: Duration::from_secs(5), { f1, f2 })Notes
- Requires
Cx::race*methods (planned). - Semantics: winners return first, losers are cancelled and drained.
scope!(cx, {
let h1 = spawn!(async { fetch_a().await });
let h2 = spawn!(async { fetch_b().await });
let (a, b) = join!(h1, h2);
(a, b)
})let value = race!(cx, timeout: Duration::from_secs(2), {
long_operation(),
async { Err(TimeoutError) },
});scope!(cx, {
scope!(cx, budget: Budget::deadline(Duration::from_secs(5)), {
// inner work with tighter budget
});
})Manual API usage (today):
let scope = cx.scope();
let (handle, stored) = scope.spawn(&mut state, &cx, |cx| async move { work(cx).await })?;
state.store_spawned_task(handle.task_id(), stored);
let result = handle.join(&cx).await?;Macro DSL (intended):
scope!(cx, {
let handle = spawn!(async { work(cx).await });
let result = handle.await;
result
})Example binaries live in examples/:
examples/macros_basic.rsexamples/macros_race.rsexamples/macros_nested.rsexamples/macros_error_handling.rs
Run with:
cargo run --example macros_basic --features proc-macros