Skip to main content

async_rs/util/
join.rs

1use std::{
2    fmt,
3    future::Future,
4    pin::Pin,
5    task::{Context, Poll},
6};
7
8/// Drive two futures concurrently, returning both outputs once both complete.
9///
10/// Both futures are polled on each wake; neither starves the other. This is a minimal,
11/// runtime-agnostic equivalent of `futures::join!` for the two-future case. The futures are boxed so
12/// the combinator needs no `unsafe` pin projection.
13pub fn join<A: Future, B: Future>(a: A, b: B) -> Join<A, B> {
14    Join {
15        a: MaybeDone::new(a),
16        b: MaybeDone::new(b),
17    }
18}
19
20/// Drive two fallible futures concurrently, short-circuiting on the first error.
21///
22/// On the first `Err`, that error is returned without polling the other future again; otherwise
23/// both `Ok` values are returned once both complete. Useful for "run these two halves until one of
24/// them fails, then tear both down": the unfinished half is cancelled when the caller drops the
25/// [`TryJoin`], which is what dropping this future on the spot amounts to.
26pub fn try_join<T1, T2, E, A, B>(a: A, b: B) -> TryJoin<A, B>
27where
28    A: Future<Output = Result<T1, E>>,
29    B: Future<Output = Result<T2, E>>,
30{
31    TryJoin {
32        a: MaybeDone::new(a),
33        b: MaybeDone::new(b),
34    }
35}
36
37/// A boxed future that retains its output once it resolves.
38struct MaybeDone<F: Future> {
39    fut: Option<Pin<Box<F>>>,
40    output: Option<F::Output>,
41}
42
43impl<F: Future> MaybeDone<F> {
44    fn new(f: F) -> Self {
45        Self {
46            fut: Some(Box::pin(f)),
47            output: None,
48        }
49    }
50
51    /// Poll the inner future if still pending, stashing its output. Returns whether it is now done.
52    fn poll(&mut self, cx: &mut Context<'_>) -> bool {
53        if let Some(fut) = self.fut.as_mut()
54            && let Poll::Ready(out) = fut.as_mut().poll(cx)
55        {
56            self.output = Some(out);
57            self.fut = None;
58        }
59        self.fut.is_none()
60    }
61
62    fn is_done(&self) -> bool {
63        self.fut.is_none()
64    }
65
66    fn take(&mut self) -> F::Output {
67        self.output.take().expect("take on a not-yet-done future")
68    }
69}
70
71impl<T, E, F: Future<Output = Result<T, E>>> MaybeDone<F> {
72    /// Poll the inner future, taking its error out if that is what it completed with.
73    fn poll_err(&mut self, cx: &mut Context<'_>) -> Option<E> {
74        if !self.poll(cx) || !matches!(self.output, Some(Err(_))) {
75            return None;
76        }
77        self.take().err()
78    }
79}
80
81impl<F: Future> fmt::Debug for MaybeDone<F> {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        f.debug_struct("MaybeDone")
84            .field("done", &self.is_done())
85            .finish()
86    }
87}
88
89/// Future returned by [`join`].
90#[must_use = "futures do nothing unless you `.await` or poll them"]
91pub struct Join<A: Future, B: Future> {
92    a: MaybeDone<A>,
93    b: MaybeDone<B>,
94}
95
96// Written out rather than derived: a derive would ask for `A: Debug, B: Debug`, which the async
97// blocks these are built from never satisfy.
98impl<A: Future, B: Future> fmt::Debug for Join<A, B> {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        f.debug_struct("Join")
101            .field("a", &self.a)
102            .field("b", &self.b)
103            .finish()
104    }
105}
106
107impl<A: Future, B: Future> Future for Join<A, B> {
108    type Output = (A::Output, B::Output);
109
110    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
111        // `Self` is `Unpin` (the futures are boxed), so we can freely take a `&mut`.
112        let this = self.get_mut();
113        let a_done = this.a.poll(cx);
114        let b_done = this.b.poll(cx);
115        if a_done && b_done {
116            Poll::Ready((this.a.take(), this.b.take()))
117        } else {
118            Poll::Pending
119        }
120    }
121}
122
123// Boxed futures make the combinators `Unpin` regardless of the inner futures.
124impl<A: Future, B: Future> Unpin for Join<A, B> {}
125
126/// Future returned by [`try_join`].
127#[must_use = "futures do nothing unless you `.await` or poll them"]
128pub struct TryJoin<A: Future, B: Future> {
129    a: MaybeDone<A>,
130    b: MaybeDone<B>,
131}
132
133impl<A: Future, B: Future> fmt::Debug for TryJoin<A, B> {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        f.debug_struct("TryJoin")
136            .field("a", &self.a)
137            .field("b", &self.b)
138            .finish()
139    }
140}
141
142impl<A: Future, B: Future> Unpin for TryJoin<A, B> {}
143
144impl<T1, T2, E, A, B> Future for TryJoin<A, B>
145where
146    A: Future<Output = Result<T1, E>>,
147    B: Future<Output = Result<T2, E>>,
148{
149    type Output = Result<(T1, T2), E>;
150
151    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
152        let this = self.get_mut();
153
154        // Poll both, short-circuiting the moment one completes with an error (which drops — cancels
155        // — the other when `this` is dropped by the caller).
156        if let Some(err) = this.a.poll_err(cx) {
157            return Poll::Ready(Err(err));
158        }
159        if let Some(err) = this.b.poll_err(cx) {
160            return Poll::Ready(Err(err));
161        }
162
163        if this.a.is_done() && this.b.is_done() {
164            let a = this.a.take().ok().expect("checked Ok");
165            let b = this.b.take().ok().expect("checked Ok");
166            Poll::Ready(Ok((a, b)))
167        } else {
168            Poll::Pending
169        }
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use crate::util::simple_block_on;
177    use std::{cell::Cell, future::poll_fn, rc::Rc};
178
179    // A future that returns Pending `pendings` times before yielding `val`.
180    fn delayed<T: Clone + 'static>(pendings: usize, val: T) -> impl Future<Output = T> {
181        let left = Rc::new(Cell::new(pendings));
182        poll_fn(move |cx: &mut Context<'_>| {
183            if left.get() == 0 {
184                Poll::Ready(val.clone())
185            } else {
186                left.set(left.get() - 1);
187                cx.waker().wake_by_ref();
188                Poll::Pending
189            }
190        })
191    }
192
193    // The types must stay Debug for the futures they are actually built from, which a derived
194    // impl would not manage: async blocks are not Debug.
195    #[test]
196    fn combinators_are_debug_over_async_blocks() {
197        fn debug<T: fmt::Debug>(t: &T) -> String {
198            format!("{t:?}")
199        }
200        debug(&join(async { 1u8 }, async { 2u8 }));
201        debug(&try_join(async { Ok::<u8, ()>(1) }, async {
202            Ok::<u8, ()>(2)
203        }));
204    }
205
206    #[test]
207    fn join_returns_both() {
208        let (a, b) = simple_block_on(join(delayed(2, 1u8), delayed(5, "x")));
209        assert_eq!(a, 1);
210        assert_eq!(b, "x");
211    }
212
213    #[test]
214    fn try_join_ok_returns_both() {
215        let out: Result<(u8, u8), ()> =
216            simple_block_on(try_join(delayed(1, Ok(1u8)), delayed(3, Ok(2u8))));
217        assert_eq!(out, Ok((1, 2)));
218    }
219
220    #[test]
221    fn try_join_short_circuits_on_error() {
222        // The error future resolves first; the other never resolves on its own, so a successful
223        // return proves try_join short-circuited and dropped (cancelled) it.
224        let other = poll_fn(|cx: &mut Context<'_>| {
225            cx.waker().wake_by_ref();
226            Poll::<Result<u8, &str>>::Pending
227        });
228        let err = delayed(1, Err::<u8, &str>("boom"));
229        let out = simple_block_on(try_join(err, other));
230        assert_eq!(out, Err("boom"));
231    }
232}