Skip to content

Commit 1db2b4e

Browse files
author
Markus Westerlind
authored
perf: Avoid an Option in the Map* futures (#2306)
* perf: Avoid an Option in the `Map*` futures * refactor: Rewrite UnfoldState with project_replace
1 parent 1661bad commit 1db2b4e

File tree

5 files changed

+51
-38
lines changed

5 files changed

+51
-38
lines changed

futures-util/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ memchr = { version = "2.2", optional = true }
4646
futures_01 = { version = "0.1.25", optional = true, package = "futures" }
4747
tokio-io = { version = "0.1.9", optional = true }
4848
pin-utils = "0.1.0"
49-
pin-project-lite = "0.2"
49+
pin-project-lite = "0.2.4"
5050

5151
[dev-dependencies]
5252
futures = { path = "../futures", features = ["async-await", "thread-pool"] }

futures-util/src/future/future/map.rs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ use crate::fns::FnOnce1;
99
pin_project! {
1010
/// Internal Map future
1111
#[project = MapProj]
12+
#[project_replace = MapProjReplace]
1213
#[derive(Debug)]
1314
#[must_use = "futures do nothing unless you `.await` or poll them"]
1415
pub enum Map<Fut, F> {
1516
Incomplete {
1617
#[pin]
1718
future: Fut,
18-
f: Option<F>,
19+
f: F,
1920
},
2021
Complete,
2122
}
@@ -24,13 +25,14 @@ pin_project! {
2425
impl<Fut, F> Map<Fut, F> {
2526
/// Creates a new Map.
2627
pub(crate) fn new(future: Fut, f: F) -> Self {
27-
Self::Incomplete { future, f: Some(f) }
28+
Self::Incomplete { future, f }
2829
}
2930
}
3031

3132
impl<Fut, F, T> FusedFuture for Map<Fut, F>
32-
where Fut: Future,
33-
F: FnOnce1<Fut::Output, Output=T>,
33+
where
34+
Fut: Future,
35+
F: FnOnce1<Fut::Output, Output = T>,
3436
{
3537
fn is_terminated(&self) -> bool {
3638
match self {
@@ -41,20 +43,24 @@ impl<Fut, F, T> FusedFuture for Map<Fut, F>
4143
}
4244

4345
impl<Fut, F, T> Future for Map<Fut, F>
44-
where Fut: Future,
45-
F: FnOnce1<Fut::Output, Output=T>,
46+
where
47+
Fut: Future,
48+
F: FnOnce1<Fut::Output, Output = T>,
4649
{
4750
type Output = T;
4851

4952
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
5053
match self.as_mut().project() {
51-
MapProj::Incomplete { future, f } => {
54+
MapProj::Incomplete { future, .. } => {
5255
let output = ready!(future.poll(cx));
53-
let f = f.take().unwrap();
54-
self.set(Self::Complete);
55-
Poll::Ready(f.call_once(output))
56-
},
57-
MapProj::Complete => panic!("Map must not be polled after it returned `Poll::Ready`"),
56+
match self.project_replace(Map::Complete) {
57+
MapProjReplace::Incomplete { f, .. } => Poll::Ready(f.call_once(output)),
58+
MapProjReplace::Complete => unreachable!(),
59+
}
60+
}
61+
MapProj::Complete => {
62+
panic!("Map must not be polled after it returned `Poll::Ready`")
63+
}
5864
}
5965
}
6066
}

futures-util/src/sink/unfold.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pin_project! {
3838
pub fn unfold<T, F, R>(init: T, function: F) -> Unfold<T, F, R> {
3939
Unfold {
4040
function,
41-
state: UnfoldState::Value(init),
41+
state: UnfoldState::Value { value: init },
4242
}
4343
}
4444

@@ -59,7 +59,7 @@ where
5959
Some(value) => (this.function)(value, item),
6060
None => panic!("start_send called without poll_ready being called first"),
6161
};
62-
this.state.set(UnfoldState::Future(future));
62+
this.state.set(UnfoldState::Future { future });
6363
Ok(())
6464
}
6565

@@ -68,7 +68,7 @@ where
6868
Poll::Ready(if let Some(future) = this.state.as_mut().project_future() {
6969
match ready!(future.poll(cx)) {
7070
Ok(state) => {
71-
this.state.set(UnfoldState::Value(state));
71+
this.state.set(UnfoldState::Value { value: state });
7272
Ok(())
7373
}
7474
Err(err) => Err(err),

futures-util/src/stream/unfold.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ where
5353
{
5454
Unfold {
5555
f,
56-
state: UnfoldState::Value(init),
56+
state: UnfoldState::Value { value: init },
5757
}
5858
}
5959

@@ -104,7 +104,9 @@ where
104104
let mut this = self.project();
105105

106106
if let Some(state) = this.state.as_mut().take_value() {
107-
this.state.set(UnfoldState::Future((this.f)(state)));
107+
this.state.set(UnfoldState::Future {
108+
future: (this.f)(state),
109+
});
108110
}
109111

110112
let step = match this.state.as_mut().project_future() {
@@ -113,7 +115,7 @@ where
113115
};
114116

115117
if let Some((item, next_state)) = step {
116-
this.state.set(UnfoldState::Value(next_state));
118+
this.state.set(UnfoldState::Value { value: next_state });
117119
Poll::Ready(Some(item))
118120
} else {
119121
this.state.set(UnfoldState::Empty);

futures-util/src/unfold_state.rs

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,37 @@
11
use core::pin::Pin;
22

3-
/// UnfoldState used for stream and sink unfolds
4-
#[derive(Debug)]
5-
pub(crate) enum UnfoldState<T, R> {
6-
Value(T),
7-
Future(/* #[pin] */ R),
8-
Empty,
3+
use pin_project_lite::pin_project;
4+
5+
pin_project! {
6+
/// UnfoldState used for stream and sink unfolds
7+
#[project = UnfoldStateProj]
8+
#[project_replace = UnfoldStateProjReplace]
9+
#[derive(Debug)]
10+
pub(crate) enum UnfoldState<T, R> {
11+
Value {
12+
value: T,
13+
},
14+
Future {
15+
#[pin]
16+
future: R,
17+
},
18+
Empty,
19+
}
920
}
1021

1122
impl<T, R> UnfoldState<T, R> {
1223
pub(crate) fn project_future(self: Pin<&mut Self>) -> Option<Pin<&mut R>> {
13-
// SAFETY Normal pin projection on the `Future` variant
14-
unsafe {
15-
match self.get_unchecked_mut() {
16-
Self::Future(f) => Some(Pin::new_unchecked(f)),
17-
_ => None,
18-
}
24+
match self.project() {
25+
UnfoldStateProj::Future { future } => Some(future),
26+
_ => None,
1927
}
2028
}
2129

2230
pub(crate) fn take_value(self: Pin<&mut Self>) -> Option<T> {
23-
// SAFETY We only move out of the `Value` variant which is not pinned
24-
match *self {
25-
Self::Value(_) => unsafe {
26-
match core::mem::replace(self.get_unchecked_mut(), UnfoldState::Empty) {
27-
UnfoldState::Value(v) => Some(v),
28-
_ => core::hint::unreachable_unchecked(),
29-
}
31+
match &*self {
32+
UnfoldState::Value { .. } => match self.project_replace(UnfoldState::Empty) {
33+
UnfoldStateProjReplace::Value { value } => Some(value),
34+
_ => unreachable!(),
3035
},
3136
_ => None,
3237
}

0 commit comments

Comments
 (0)