Crate scoped_join_set

Crate scoped_join_set 

Source
Expand description

A scoped wrapper around tokio::task::JoinSet allowing non-'static futures to be spawned safely.

§What ScopedJoinSet provides

Tokio requires all spawned tasks to be 'static. ScopedJoinSet lifts this restriction by:

  • allocating each user future and its output slot on the heap,
  • pinning the wrapper future so it never moves,
  • unsafely casting it to 'static (sound because the allocation is kept alive until the task completes),
  • storing each task’s output in Box<Option<T>>,
  • retrieving and deallocating that output when the task completes.

This lets users write:

§Example

use scoped_join_set::ScopedJoinSet;

#[tokio::main]
async fn main() {
    let temporary_value = Box::new(5);
    let value_ref = &temporary_value;

    let mut set = ScopedJoinSet::<u32>::new();

    set.spawn(async {
        println!("I have a {value_ref}");
        **value_ref
    });

    while let Some(result) = set.join_next().await {
        assert_eq!(result.unwrap(), *temporary_value);
    }
    set.shutdown().await;
}

without requiring the future to be 'static.

§How it works internally

  1. spawn() allocates a Box<MaybeUninit<T>> for the result and leaks it.
  2. The user future is wrapped in WriteOutput, which writes its output into this slot.
  3. The wrapper future is pinned and unsafely converted to 'static.
  4. Tokio polls the wrapper and returns a raw pointer identifying the output slot.
  5. join_next() converts that pointer back, reads the output, and frees the allocation.

No hashmaps or external bookkeeping structures are used—the pointer returned by each completed task uniquely identifies its output slot.
There is no self-referential struct and no dependence on pinning beyond ensuring that the wrapped future remains immovable.

§Important: Explicit Shutdown Required

A ScopedJoinSet must be terminated explicitly using either

before it is dropped.

Dropping a ScopedJoinSet implicitly (e.g., letting it go out of scope without calling one of the methods above) is considered a logic error.

To prevent accidental misuse — which could otherwise cause tasks holding references into the stack to outlive their scope — the destructor prints an error message and aborts the process.

Structs§

ScopedJoinSet
A scoped task set analogous to tokio::task::JoinSet but accepting non-'static futures.

Enums§

JoinError
Errors returned by scoped tasks.