Skip to content

Commit 1753765

Browse files
committed
Cleaned up First* implementations:
- Removed FusedIterator; .fuse() is equivalent. - FirstOk is more explicit about its use of FusedIterator. - This design will have to wait for rust-lang#2111 to be resolved; currently the contract of FusedFuture is not strict enough for our needs.
1 parent d980367 commit 1753765

File tree

3 files changed

+46
-51
lines changed

3 files changed

+46
-51
lines changed

futures-util/src/future/first.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::future::Either;
22
use core::pin::Pin;
3-
use futures_core::future::{FusedFuture, Future};
3+
use futures_core::future::Future;
44
use futures_core::task::{Context, Poll};
55
use pin_utils::unsafe_pinned;
66

@@ -12,6 +12,8 @@ pub struct First<F1, F2> {
1212
future2: F2,
1313
}
1414

15+
impl<F1: Unpin, F2: Unpin> Unpin for First<F1, F2> {}
16+
1517
impl<F1, F2> First<F1, F2> {
1618
unsafe_pinned!(future1: F1);
1719
unsafe_pinned!(future2: F2);
@@ -32,12 +34,9 @@ impl<F1: Future, F2: Future> Future for First<F1, F2> {
3234
}
3335
}
3436

35-
impl<F1: FusedFuture, F2: FusedFuture> FusedFuture for First<F1, F2> {
36-
#[inline]
37-
fn is_terminated(&self) -> bool {
38-
self.future1.is_terminated() || self.future2.is_terminated()
39-
}
40-
}
37+
// We don't provide FusedFuture, because the overhead of implementing it (
38+
// which requires a separate bool or Option field) is precisely the same as
39+
// calling .fuse()
4140

4241
/// Waits for either one of two differently-typed futures to complete.
4342
///
@@ -52,7 +51,7 @@ impl<F1: FusedFuture, F2: FusedFuture> FusedFuture for First<F1, F2> {
5251
/// wrapped version of them.
5352
///
5453
/// Also note that if both this and the second future have the same
55-
/// output type you can use the `Either::factor_first` method to
54+
/// output type you can use the `Either::into_immer` method to
5655
/// conveniently extract out the value at the end.
5756
pub fn first<F1, F2>(future1: F1, future2: F2) -> First<F1, F2> {
5857
First { future1, future2 }

futures-util/src/future/first_all.rs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use core::iter::FromIterator;
22
use core::pin::Pin;
3-
use futures_core::future::{FusedFuture, Future};
3+
use futures_core::future::Future;
44
use futures_core::task::{Context, Poll};
55

66
/// Future for the [`first_all()`] function.
@@ -32,26 +32,15 @@ impl<F: Future> Future for FirstAll<F> {
3232
Poll::Pending => None,
3333
}
3434
}) {
35-
Some(out) => {
36-
// Safety: safe because vec clears in place
37-
this.futures.clear();
38-
Poll::Ready(out)
39-
}
35+
Some(out) => Poll::Ready(out),
4036
None => Poll::Pending,
4137
}
4238
}
4339
}
4440

45-
impl<F: FusedFuture> FusedFuture for FirstAll<F> {
46-
#[inline]
47-
fn is_terminated(&self) -> bool {
48-
// Logic: it's possible for a future to independently become
49-
// terminated, before it returns Ready, so we're not terminated unless
50-
// *all* of our inner futures are terminated. When our own poll returns
51-
// Ready, this vector is cleared, so the logic works correctly.
52-
self.futures.iter().all(|fut| fut.is_terminated())
53-
}
54-
}
41+
// We don't provide FusedFuture, because the overhead of implementing it (
42+
// which requires clearing the vector after Ready is returned) is precisely
43+
// the same as using .fuse()
5544

5645
impl<Fut: Future> FromIterator<Fut> for FirstAll<Fut> {
5746
fn from_iter<T: IntoIterator<Item = Fut>>(iter: T) -> Self {

futures-util/src/future/first_ok.rs

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,10 @@ impl<F: FusedFuture + TryFuture> Future for FirstOk<F> {
2323
#[inline]
2424
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2525
// Basic logic diagram:
26-
// - If all existing futures are terminated, return Pending. This means
27-
// someone polled after this future returned ready, or that this
28-
// future will never return ready because a future spuriously
29-
// terminated itself.
30-
// - If a future returns Ok, clear the vector (this is safe because
31-
// vec drops in place), then return that value. We clear the vector
32-
// so that our FusedFuture impl, which checks `are all futures
33-
// terminated`, works correctly.
26+
// - If all existing futures are terminated, return Pending.
27+
// - If a future returns Ok, return that value.
3428
// - If all existing futures BECOME terminated while polling them, and
35-
// an error was returned, return the final error; otherwise return
36-
// pending.
29+
// an error was returned, return the final error.
3730

3831
/// Helper enum to track our state as we poll each future
3932
enum State<E> {
@@ -64,19 +57,15 @@ impl<F: FusedFuture + TryFuture> Future for FirstOk<F> {
6457
}
6558

6659
let mut state = State::NoErrors;
67-
let this = self.get_mut();
68-
for fut in this.futures.iter_mut() {
60+
61+
for fut in self.get_mut().futures.iter_mut() {
6962
if !fut.is_terminated() {
7063
// Safety: we promise that the future is never moved out of the vec,
7164
// and that the vec never reallocates once FirstOk has been created
7265
// (specifically after the first poll)
7366
let pinned = unsafe { Pin::new_unchecked(fut) };
7467
match pinned.try_poll(cx) {
75-
Poll::Ready(Ok(out)) => {
76-
// Safety: safe because vec clears in place
77-
this.futures.clear();
78-
return Poll::Ready(Ok(out));
79-
}
68+
Poll::Ready(Ok(out)) => return Poll::Ready(Ok(out)),
8069
Poll::Ready(Err(err)) => state.apply_error(err),
8170
Poll::Pending => state.apply_pending(),
8271
}
@@ -85,17 +74,21 @@ impl<F: FusedFuture + TryFuture> Future for FirstOk<F> {
8574

8675
match state {
8776
SeenError(err) => Poll::Ready(Err(err)),
88-
NoErrors | SeenPending => Poll::Pending,
77+
SeenPending => Poll::Pending,
78+
// This is unreachable unless every future in the vec returned
79+
// is_terminated, which means that we must have returned Ready on
80+
// a previous poll, or the vec is empty, which we disallow in the
81+
// first_ok constructor, or that we were initialized with futures
82+
// that have already returned Ready, which is possibly unsound
83+
// (given !Unpin futures) but certainly breaks first_ok contract.
84+
NoErrors => panic!("All futures in the FirstOk terminated without a result being found. Did you re-poll after Ready?"),
8985
}
9086
}
9187
}
9288

93-
impl<F: FusedFuture + TryFuture> FusedFuture for FirstOk<F> {
94-
#[inline]
95-
fn is_terminated(&self) -> bool {
96-
self.futures.iter().all(|fut| fut.is_terminated())
97-
}
98-
}
89+
// We don't provide FusedFuture, because the overhead of implementing it (
90+
// which requires clearing the vector after Ready is returned) is precisely
91+
// the same as using .fuse()
9992

10093
impl<Fut: FusedFuture + TryFuture> FromIterator<Fut> for FirstOk<Fut> {
10194
fn from_iter<T: IntoIterator<Item = Fut>>(iter: T) -> Self {
@@ -121,13 +114,27 @@ impl<Fut: FusedFuture + TryFuture> FromIterator<Fut> for FirstOk<Fut> {
121114
///
122115
/// # Panics
123116
///
124-
/// This function will panic if the iterator specified contains no items.
117+
/// This function will panic if the iterator specified contains no items, or
118+
/// if any of the futures have already been terminated.
125119
pub fn first_ok<I>(futures: I) -> FirstOk<I::Item>
126120
where
127121
I: IntoIterator,
128122
I::Item: FusedFuture + TryFuture,
129123
{
130-
let futures = Vec::from_iter(futures);
131-
assert!(!futures.is_empty(), "Need at least 1 future for first_ok");
124+
let futures: Vec<_> = futures
125+
.into_iter()
126+
.inspect(|fut| {
127+
assert!(
128+
!fut.is_terminated(),
129+
"Can't call first_ok with a terminated future"
130+
)
131+
})
132+
.collect();
133+
134+
assert!(
135+
!futures.is_empty(),
136+
"Need at least 1 non-terminated future for first_ok"
137+
);
138+
132139
FirstOk { futures }
133140
}

0 commit comments

Comments
 (0)