I've been educating myself about Rust's implementation of async/await lately, and my understanding is that Futures can schedule themselves to be polled at a later moment by calling wake() on the Waker, which will cause the executor to call poll again.
Assuming that's correct, I'm not exactly sure what the executor would do with a Poll::Ready<T> when the T is anything other than ().
Indeed, the futures's RFC mentions that:
executors provide the ability to create tasks from
()-producingFutures
However, many runtimes provide a free-standing function to spawn tasks whose signature, modulo any Send, Sync and 'statics, is something like
pub fn spawn<F: Future>(future: F) -> JoinHandle<F::Output> {}
where JoinHandle<T> is itself a future that resolves to T.
I've been thinking about how this could be implemented under the assumption that the executor can only spawn ()-producing Futures.
In pseudo-code, assuming there's a global EXECUTOR, this could be:
pub fn spawn<F: Future>(future: F) -> JoinHandle<F::Output> {
let (tx, rx) = oneshot::channel();
let future = async move {
let out = future.await;
let _ = tx.send(out);
};
EXECUTOR.spawn(future);
JoinHandle::new(rx)
}
pub struct JoinHandle<T> {
rx: oneshot::Receiver<T>
}
impl<T> Future for JoinHandle<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.rx.try_recv() {
Ok(value) => Poll::Ready(value),
Err(Error::Empty) => Poll::Pending,
Err(Error::Closed) => unreachable!(),
}
}
}
But I doubt tokio, async-std or smol are actually using channels to do this.
Am I completely off? What would be the proper way to do this?