Skip to content

Commit 90c3e9a

Browse files
authored
Rollup merge of #91645 - ibraheemdev:future-join, r=joshtriplett
Implement `core::future::join!` `join!` polls multiple futures concurrently and returns their outputs. ```rust async fn run() { let (a, b) = join!(async { 0 }, async { 1 }); } ``` cc `@rust-lang/wg-async-foundations`
2 parents f820496 + 5478f43 commit 90c3e9a

File tree

4 files changed

+239
-0
lines changed

4 files changed

+239
-0
lines changed

library/core/src/future/join.rs

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#![allow(unused_imports)] // items are used by the macro
2+
3+
use crate::cell::UnsafeCell;
4+
use crate::future::{poll_fn, Future};
5+
use crate::mem;
6+
use crate::pin::Pin;
7+
use crate::task::{Context, Poll};
8+
9+
/// Polls multiple futures simultaneously, returning a tuple
10+
/// of all results once complete.
11+
///
12+
/// While `join!(a, b)` is similar to `(a.await, b.await)`,
13+
/// `join!` polls both futures concurrently and is therefore more efficient.
14+
///
15+
/// # Examples
16+
///
17+
/// ```
18+
/// #![feature(future_join, future_poll_fn)]
19+
///
20+
/// use std::future::join;
21+
///
22+
/// async fn one() -> usize { 1 }
23+
/// async fn two() -> usize { 2 }
24+
///
25+
/// # let _ = async {
26+
/// let x = join!(one(), two()).await;
27+
/// assert_eq!(x, (1, 2));
28+
/// # };
29+
/// ```
30+
///
31+
/// `join!` is variadic, so you can pass any number of futures:
32+
///
33+
/// ```
34+
/// #![feature(future_join, future_poll_fn)]
35+
///
36+
/// use std::future::join;
37+
///
38+
/// async fn one() -> usize { 1 }
39+
/// async fn two() -> usize { 2 }
40+
/// async fn three() -> usize { 3 }
41+
///
42+
/// # let _ = async {
43+
/// let x = join!(one(), two(), three()).await;
44+
/// assert_eq!(x, (1, 2, 3));
45+
/// # };
46+
/// ```
47+
#[unstable(feature = "future_join", issue = "91642")]
48+
pub macro join {
49+
( $($fut:expr),* $(,)?) => {
50+
join! { @count: (), @futures: {}, @rest: ($($fut,)*) }
51+
},
52+
// Recurse until we have the position of each future in the tuple
53+
(
54+
// A token for each future that has been expanded: "_ _ _"
55+
@count: ($($count:tt)*),
56+
// Futures and their positions in the tuple: "{ a => (_), b => (_ _)) }"
57+
@futures: { $($fut:tt)* },
58+
// Take a future from @rest to expand
59+
@rest: ($current:expr, $($rest:tt)*)
60+
) => {
61+
join! {
62+
@count: ($($count)* _),
63+
@futures: { $($fut)* $current => ($($count)*), },
64+
@rest: ($($rest)*)
65+
}
66+
},
67+
// Now generate the output future
68+
(
69+
@count: ($($count:tt)*),
70+
@futures: {
71+
$( $(@$f:tt)? $fut:expr => ( $($pos:tt)* ), )*
72+
},
73+
@rest: ()
74+
) => {
75+
async move {
76+
let mut futures = ( $( MaybeDone::Future($fut), )* );
77+
78+
poll_fn(move |cx| {
79+
let mut done = true;
80+
81+
$(
82+
let ( $($pos,)* fut, .. ) = &mut futures;
83+
84+
// SAFETY: The futures are never moved
85+
done &= unsafe { Pin::new_unchecked(fut).poll(cx).is_ready() };
86+
)*
87+
88+
if done {
89+
// Extract all the outputs
90+
Poll::Ready(($({
91+
let ( $($pos,)* fut, .. ) = &mut futures;
92+
93+
fut.take_output().unwrap()
94+
}),*))
95+
} else {
96+
Poll::Pending
97+
}
98+
}).await
99+
}
100+
}
101+
}
102+
103+
/// Future used by `join!` that stores it's output to
104+
/// be later taken and doesn't panic when polled after ready.
105+
///
106+
/// This type is public in a private module for use by the macro.
107+
#[allow(missing_debug_implementations)]
108+
#[unstable(feature = "future_join", issue = "91642")]
109+
pub enum MaybeDone<F: Future> {
110+
Future(F),
111+
Done(F::Output),
112+
Took,
113+
}
114+
115+
#[unstable(feature = "future_join", issue = "91642")]
116+
impl<F: Future> MaybeDone<F> {
117+
pub fn take_output(&mut self) -> Option<F::Output> {
118+
match &*self {
119+
MaybeDone::Done(_) => match mem::replace(self, Self::Took) {
120+
MaybeDone::Done(val) => Some(val),
121+
_ => unreachable!(),
122+
},
123+
_ => None,
124+
}
125+
}
126+
}
127+
128+
#[unstable(feature = "future_join", issue = "91642")]
129+
impl<F: Future> Future for MaybeDone<F> {
130+
type Output = ();
131+
132+
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
133+
// SAFETY: pinning in structural for `f`
134+
unsafe {
135+
match self.as_mut().get_unchecked_mut() {
136+
MaybeDone::Future(f) => match Pin::new_unchecked(f).poll(cx) {
137+
Poll::Ready(val) => self.set(Self::Done(val)),
138+
Poll::Pending => return Poll::Pending,
139+
},
140+
MaybeDone::Done(_) => {}
141+
MaybeDone::Took => unreachable!(),
142+
}
143+
}
144+
145+
Poll::Ready(())
146+
}
147+
}

library/core/src/future/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@ use crate::{
1111

1212
mod future;
1313
mod into_future;
14+
mod join;
1415
mod pending;
1516
mod poll_fn;
1617
mod ready;
1718

1819
#[stable(feature = "futures_api", since = "1.36.0")]
1920
pub use self::future::Future;
2021

22+
#[unstable(feature = "future_join", issue = "91642")]
23+
pub use self::join::join;
24+
2125
#[unstable(feature = "into_future", issue = "67644")]
2226
pub use into_future::IntoFuture;
2327

library/core/tests/future.rs

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use std::future::{join, Future};
2+
use std::pin::Pin;
3+
use std::sync::Arc;
4+
use std::task::{Context, Poll, Wake};
5+
use std::thread;
6+
7+
struct PollN {
8+
val: usize,
9+
polled: usize,
10+
num: usize,
11+
}
12+
13+
impl Future for PollN {
14+
type Output = usize;
15+
16+
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
17+
self.polled += 1;
18+
19+
if self.polled == self.num {
20+
return Poll::Ready(self.val);
21+
}
22+
23+
cx.waker().wake_by_ref();
24+
Poll::Pending
25+
}
26+
}
27+
28+
fn poll_n(val: usize, num: usize) -> PollN {
29+
PollN { val, num, polled: 0 }
30+
}
31+
32+
#[test]
33+
fn test_join() {
34+
block_on(async move {
35+
let x = join!(async { 0 }).await;
36+
assert_eq!(x, 0);
37+
38+
let x = join!(async { 0 }, async { 1 }).await;
39+
assert_eq!(x, (0, 1));
40+
41+
let x = join!(async { 0 }, async { 1 }, async { 2 }).await;
42+
assert_eq!(x, (0, 1, 2));
43+
44+
let x = join!(
45+
poll_n(0, 1),
46+
poll_n(1, 5),
47+
poll_n(2, 2),
48+
poll_n(3, 1),
49+
poll_n(4, 2),
50+
poll_n(5, 3),
51+
poll_n(6, 4),
52+
poll_n(7, 1)
53+
)
54+
.await;
55+
assert_eq!(x, (0, 1, 2, 3, 4, 5, 6, 7));
56+
57+
let y = String::new();
58+
let x = join!(async {
59+
println!("{}", &y);
60+
1
61+
})
62+
.await;
63+
assert_eq!(x, 1);
64+
});
65+
}
66+
67+
fn block_on(fut: impl Future) {
68+
struct Waker;
69+
impl Wake for Waker {
70+
fn wake(self: Arc<Self>) {
71+
thread::current().unpark()
72+
}
73+
}
74+
75+
let waker = Arc::new(Waker).into();
76+
let mut cx = Context::from_waker(&waker);
77+
let mut fut = Box::pin(fut);
78+
79+
loop {
80+
match fut.as_mut().poll(&mut cx) {
81+
Poll::Ready(_) => break,
82+
Poll::Pending => thread::park(),
83+
}
84+
}
85+
}

library/core/tests/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
#![feature(flt2dec)]
3030
#![feature(fmt_internals)]
3131
#![feature(float_minimum_maximum)]
32+
#![feature(future_join)]
33+
#![feature(future_poll_fn)]
3234
#![feature(array_from_fn)]
3335
#![feature(hashmap_internals)]
3436
#![feature(try_find)]
@@ -94,6 +96,7 @@ mod clone;
9496
mod cmp;
9597
mod const_ptr;
9698
mod fmt;
99+
mod future;
97100
mod hash;
98101
mod intrinsics;
99102
mod iter;

0 commit comments

Comments
 (0)