Generics, Associated Types, and Task Type Erasure
The public API preserves exact types
spawn<F> is generic over the future and returns JoinHandle<F::Output>.
Channels are Sender<T> and Receiver<T>. Timeouts wrap a concrete future.
Async I/O extension methods are generic over buffers and operations. Callers
retain typed values without serialization or Any downcasts.
The Future associated type means one concrete future type has one output
type. JoinHandle<T> can therefore expose Result<T, JoinError> even after the
runtime has erased the future from its queues.
Generic allocation, uniform queue
At construction, Cell<T: Future, S> knows the exact future and scheduler
types. Its stage is:
enum Stage<T: Future> {
Running(T),
Finished(Result<T::Output, JoinError>),
Consumed,
}
The common first field is a non-generic header containing state and a pointer
to a static vtable specialized for T, S. Scheduler queues store RawTask, a
pointer to that header. Vtable functions recover layout offsets and invoke the
right poll/drop/output code.
This is manual type erasure, closer to how a trait object works than to an enum.
It avoids boxing a second dyn Future<Output = ...> abstraction while allowing
outputs of every type.
Scheduler behavior is generic too
Internal task handles are parameterized by a Schedule implementation.
Current-thread, multi-thread, and blocking tasks reuse the task harness while
choosing different submission behavior.
Dynamic behavior stays behind small handles
At the public boundary, users normally compose concrete future types that the compiler can inline. At the scheduler boundary, Tokio deliberately erases them because heterogeneous queues require one representation.
That is the recurring pattern throughout this book:
Preserve types while they encode useful caller relationships; erase them at the exact runtime collection boundary that cannot remain homogeneous.