use std::{
fmt,
future::Future,
pin::Pin,
task::{Context, Poll},
};
pub fn join<A: Future, B: Future>(a: A, b: B) -> Join<A, B> {
Join {
a: MaybeDone::new(a),
b: MaybeDone::new(b),
}
}
pub fn try_join<T1, T2, E, A, B>(a: A, b: B) -> TryJoin<A, B>
where
A: Future<Output = Result<T1, E>>,
B: Future<Output = Result<T2, E>>,
{
TryJoin {
a: MaybeDone::new(a),
b: MaybeDone::new(b),
}
}
struct MaybeDone<F: Future> {
fut: Option<Pin<Box<F>>>,
output: Option<F::Output>,
}
impl<F: Future> MaybeDone<F> {
fn new(f: F) -> Self {
Self {
fut: Some(Box::pin(f)),
output: None,
}
}
fn poll(&mut self, cx: &mut Context<'_>) -> bool {
if let Some(fut) = self.fut.as_mut()
&& let Poll::Ready(out) = fut.as_mut().poll(cx)
{
self.output = Some(out);
self.fut = None;
}
self.fut.is_none()
}
fn is_done(&self) -> bool {
self.fut.is_none()
}
fn take(&mut self) -> F::Output {
self.output.take().expect("take on a not-yet-done future")
}
}
impl<T, E, F: Future<Output = Result<T, E>>> MaybeDone<F> {
fn poll_err(&mut self, cx: &mut Context<'_>) -> Option<E> {
if !self.poll(cx) || !matches!(self.output, Some(Err(_))) {
return None;
}
self.take().err()
}
}
impl<F: Future> fmt::Debug for MaybeDone<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MaybeDone")
.field("done", &self.is_done())
.finish()
}
}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Join<A: Future, B: Future> {
a: MaybeDone<A>,
b: MaybeDone<B>,
}
impl<A: Future, B: Future> fmt::Debug for Join<A, B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Join")
.field("a", &self.a)
.field("b", &self.b)
.finish()
}
}
impl<A: Future, B: Future> Future for Join<A, B> {
type Output = (A::Output, B::Output);
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
let a_done = this.a.poll(cx);
let b_done = this.b.poll(cx);
if a_done && b_done {
Poll::Ready((this.a.take(), this.b.take()))
} else {
Poll::Pending
}
}
}
impl<A: Future, B: Future> Unpin for Join<A, B> {}
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct TryJoin<A: Future, B: Future> {
a: MaybeDone<A>,
b: MaybeDone<B>,
}
impl<A: Future, B: Future> fmt::Debug for TryJoin<A, B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TryJoin")
.field("a", &self.a)
.field("b", &self.b)
.finish()
}
}
impl<A: Future, B: Future> Unpin for TryJoin<A, B> {}
impl<T1, T2, E, A, B> Future for TryJoin<A, B>
where
A: Future<Output = Result<T1, E>>,
B: Future<Output = Result<T2, E>>,
{
type Output = Result<(T1, T2), E>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
if let Some(err) = this.a.poll_err(cx) {
return Poll::Ready(Err(err));
}
if let Some(err) = this.b.poll_err(cx) {
return Poll::Ready(Err(err));
}
if this.a.is_done() && this.b.is_done() {
let a = this.a.take().ok().expect("checked Ok");
let b = this.b.take().ok().expect("checked Ok");
Poll::Ready(Ok((a, b)))
} else {
Poll::Pending
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::simple_block_on;
use std::{cell::Cell, future::poll_fn, rc::Rc};
fn delayed<T: Clone + 'static>(pendings: usize, val: T) -> impl Future<Output = T> {
let left = Rc::new(Cell::new(pendings));
poll_fn(move |cx: &mut Context<'_>| {
if left.get() == 0 {
Poll::Ready(val.clone())
} else {
left.set(left.get() - 1);
cx.waker().wake_by_ref();
Poll::Pending
}
})
}
#[test]
fn combinators_are_debug_over_async_blocks() {
fn debug<T: fmt::Debug>(t: &T) -> String {
format!("{t:?}")
}
debug(&join(async { 1u8 }, async { 2u8 }));
debug(&try_join(async { Ok::<u8, ()>(1) }, async {
Ok::<u8, ()>(2)
}));
}
#[test]
fn join_returns_both() {
let (a, b) = simple_block_on(join(delayed(2, 1u8), delayed(5, "x")));
assert_eq!(a, 1);
assert_eq!(b, "x");
}
#[test]
fn try_join_ok_returns_both() {
let out: Result<(u8, u8), ()> =
simple_block_on(try_join(delayed(1, Ok(1u8)), delayed(3, Ok(2u8))));
assert_eq!(out, Ok((1, 2)));
}
#[test]
fn try_join_short_circuits_on_error() {
let other = poll_fn(|cx: &mut Context<'_>| {
cx.waker().wake_by_ref();
Poll::<Result<u8, &str>>::Pending
});
let err = delayed(1, Err::<u8, &str>("boom"));
let out = simple_block_on(try_join(err, other));
assert_eq!(out, Err("boom"));
}
}