Skip to content

Commit f963a8a

Browse files
Matthias247cramertj
authored andcommitted
Create the Pin for selecting on !Unpin futures lazily inside the poll function
1 parent 3953c7d commit f963a8a

File tree

2 files changed

+59
-4
lines changed

2 files changed

+59
-4
lines changed

futures-select-macro/src/lib.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,21 +177,28 @@ pub fn select(input: TokenStream) -> TokenStream {
177177
// This prevents creating redundant stack space
178178
// for them.
179179
// Passing Futures by path requires those Futures to implement Unpin.
180+
// We check for this condition here in order to be able to
181+
// safely use Pin::new_unchecked(&mut #path) later on.
182+
future_let_bindings.push(quote! {
183+
#futures_crate::async_await::assert_fused_future(&mut #path);
184+
#futures_crate::async_await::assert_unpin(&mut #path);
185+
});
180186
path
181187
},
182188
_ => {
183189
// Bind and pin the resulting Future on the stack. This is
184190
// necessary to support direct select! calls on !Unpin
185-
// Futures.
191+
// Futures. The Future is not explicitly pinned here with
192+
// a Pin call, but assumed as pinned. The actual Pin is
193+
// created inside the poll() function below to defer the
194+
// creation of the temporary pointer, which would otherwise
195+
// increase the size of the generated Future.
186196
// Safety: This is safe since the lifetime of the Future
187197
// is totally constraint to the lifetime of the select!
188198
// expression, and the Future can't get moved inside it
189199
// (it is shadowed).
190200
future_let_bindings.push(quote! {
191201
let mut #variant_name = #expr;
192-
let mut #variant_name = unsafe {
193-
::core::pin::Pin::new_unchecked(&mut #variant_name)
194-
};
195202
});
196203
parse_quote! { #variant_name }
197204
}
@@ -203,8 +210,19 @@ pub fn select(input: TokenStream) -> TokenStream {
203210
// to use for polling that individual future. These will then be put in an array.
204211
let poll_functions = bound_future_names.iter().zip(variant_names.iter())
205212
.map(|(bound_future_name, variant_name)| {
213+
// Below we lazily create the Pin on the Future below.
214+
// This is done in order to avoid allocating memory in the generator
215+
// for the Pin variable.
216+
// Safety: This is safe because one of the following condition applies:
217+
// 1. The Future is passed by the caller by name, and we assert that
218+
// it implements Unpin.
219+
// 2. The Future is created in scope of the select! function and will
220+
// not be moved for the duration of it. It is thereby stack-pinned
206221
quote! {
207222
let mut #variant_name = |__cx: &mut #futures_crate::task::Context<'_>| {
223+
let mut #bound_future_name = unsafe {
224+
::core::pin::Pin::new_unchecked(&mut #bound_future_name)
225+
};
208226
if #futures_crate::future::FusedFuture::is_terminated(&#bound_future_name) {
209227
None
210228
} else {

futures/tests/async_await_macros.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,24 @@ fn select_on_non_unpin_expressions() {
169169
5
170170
}};
171171

172+
let res = block_on(async {
173+
let select_res;
174+
select! {
175+
value_1 = make_non_unpin_fut().fuse() => { select_res = value_1 },
176+
value_2 = make_non_unpin_fut().fuse() => { select_res = value_2 },
177+
};
178+
select_res
179+
});
180+
assert_eq!(res, 5);
181+
}
182+
183+
#[test]
184+
fn select_on_non_unpin_expressions_with_default() {
185+
// The returned Future is !Unpin
186+
let make_non_unpin_fut = || { async {
187+
5
188+
}};
189+
172190
let res = block_on(async {
173191
let select_res;
174192
select! {
@@ -181,6 +199,25 @@ fn select_on_non_unpin_expressions() {
181199
assert_eq!(res, 5);
182200
}
183201

202+
#[test]
203+
fn select_on_non_unpin_size() {
204+
// The returned Future is !Unpin
205+
let make_non_unpin_fut = || { async {
206+
5
207+
}};
208+
209+
let fut = async {
210+
let select_res;
211+
select! {
212+
value_1 = make_non_unpin_fut().fuse() => { select_res = value_1 },
213+
value_2 = make_non_unpin_fut().fuse() => { select_res = value_2 },
214+
};
215+
select_res
216+
};
217+
218+
assert_eq!(48, std::mem::size_of_val(&fut));
219+
}
220+
184221
#[test]
185222
fn select_can_be_used_as_expression() {
186223
block_on(async {

0 commit comments

Comments
 (0)