Context
WaitGroup is a low-level lifetime barrier: cloned RAII handles represent outstanding work, and waiters observe when all handles are gone. It deliberately does not own tasks, collect results, propagate errors, or define cancellation and parent-child relationships.
Some use cases need a higher-level abstraction for a group of related asynchronous tasks. That should be designed as a separate primitive rather than changing WaitGroup. ForkJoin was an initial placeholder, not a proposed public name.
Ecosystem survey
- Rust's
tokio::task::JoinSet<T> owns runtime-spawned tasks, yields homogeneous results in completion order, and aborts remaining tasks when dropped. It is primarily controlled by one mutable collection owner.
- Rust's
tokio_util::task::TaskTracker is cloneable and supports registration without mutable access. Waiting requires the tracker to be both closed and empty; completed tasks release their storage immediately, results are not retained, and dropping the tracker does not abort tasks.
- Go's
errgroup.Group groups goroutines belonging to one operation, waits for them, returns the first non-nil error, can cancel a derived context on failure, and optionally limits concurrency.
- Java's
StructuredTaskScope makes one owner responsible for a lexical task scope. It forks subtasks, joins them under an explicit policy, prevents new forks after joining, and closes by cancelling and waiting for unfinished subtasks.
- Swift's
TaskGroup is a non-escaping structured scope for dynamically created child tasks. Children inherit parent context, results form an asynchronous sequence, and the scope cannot return while children remain.
- Kotlin's
coroutineScope establishes a parent-child task tree: the parent waits for children, while failure and cancellation propagate through the scope.
These examples separate into at least three families: result-owning task collections (JoinSet), shareable lifecycle trackers (TaskTracker), and structured lexical scopes (StructuredTaskScope, Swift TaskGroup, Kotlin coroutineScope). Go's errgroup combines lifecycle tracking with failure policy.
Questions to answer
- Is the target a runtime-agnostic future tracker, or may it spawn and therefore depend on a runtime?
- Is registration shareable with child tasks, restricted to one owner, or lexically scoped so handles cannot escape?
- Does joining only signal quiescence, return one result at a time, collect all results, or reduce them through a policy?
- How are task errors and panics represented, and should the first failure cancel siblings?
- What does dropping the owner do: detach, cancel, abort, or synchronously/asynchronously wait?
- Is there an explicit
close transition that prevents new top-level work? Can running children register nested work after close?
- Is bounded concurrency part of this primitive or an orthogonal concern?
- Does the design require one join owner, or are multiple completion observers meaningful?
Naming
The name should follow the selected contract rather than lead it:
TaskGroup suggests structured child ownership and possibly result collection.
JoinSet suggests an owned collection with per-task results.
TaskTracker suggests shared lifecycle observation without task ownership.
TaskScope suggests lexical structure and non-escaping children.
No name or API is proposed yet. The next step is to identify concrete Asyncband use cases and choose which semantic family, if any, belongs in a runtime-agnostic synchronization crate.
Context
WaitGroupis a low-level lifetime barrier: cloned RAII handles represent outstanding work, and waiters observe when all handles are gone. It deliberately does not own tasks, collect results, propagate errors, or define cancellation and parent-child relationships.Some use cases need a higher-level abstraction for a group of related asynchronous tasks. That should be designed as a separate primitive rather than changing
WaitGroup.ForkJoinwas an initial placeholder, not a proposed public name.Ecosystem survey
tokio::task::JoinSet<T>owns runtime-spawned tasks, yields homogeneous results in completion order, and aborts remaining tasks when dropped. It is primarily controlled by one mutable collection owner.tokio_util::task::TaskTrackeris cloneable and supports registration without mutable access. Waiting requires the tracker to be both closed and empty; completed tasks release their storage immediately, results are not retained, and dropping the tracker does not abort tasks.errgroup.Groupgroups goroutines belonging to one operation, waits for them, returns the first non-nil error, can cancel a derived context on failure, and optionally limits concurrency.StructuredTaskScopemakes one owner responsible for a lexical task scope. It forks subtasks, joins them under an explicit policy, prevents new forks after joining, and closes by cancelling and waiting for unfinished subtasks.TaskGroupis a non-escaping structured scope for dynamically created child tasks. Children inherit parent context, results form an asynchronous sequence, and the scope cannot return while children remain.coroutineScopeestablishes a parent-child task tree: the parent waits for children, while failure and cancellation propagate through the scope.These examples separate into at least three families: result-owning task collections (
JoinSet), shareable lifecycle trackers (TaskTracker), and structured lexical scopes (StructuredTaskScope, SwiftTaskGroup, KotlincoroutineScope). Go'serrgroupcombines lifecycle tracking with failure policy.Questions to answer
closetransition that prevents new top-level work? Can running children register nested work after close?Naming
The name should follow the selected contract rather than lead it:
TaskGroupsuggests structured child ownership and possibly result collection.JoinSetsuggests an owned collection with per-task results.TaskTrackersuggests shared lifecycle observation without task ownership.TaskScopesuggests lexical structure and non-escaping children.No name or API is proposed yet. The next step is to identify concrete Asyncband use cases and choose which semantic family, if any, belongs in a runtime-agnostic synchronization crate.