Skip to content

Commit 4a49fbf

Browse files
Matthias247cramertj
authored andcommitted
Allow !Unpin Futures in select!
This adds support for directly `select!`ing on !Unpin Futures as long as those Futures are returned by expressions by stack-pinning the Futures. For the common case where the futures are the result of an `async fn` call this will increase the ergonomics of the macro a lot, since no explicit binding and stack pinning is required. Partially resolves #1811 (it does not affect the borrow-checking requirements)
1 parent 0d56731 commit 4a49fbf

File tree

3 files changed

+44
-6
lines changed

3 files changed

+44
-6
lines changed

futures-select-macro/src/lib.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,25 @@ pub fn select(input: TokenStream) -> TokenStream {
172172
.zip(variant_names.iter())
173173
.map(|(expr, variant_name)| {
174174
match expr {
175-
// Don't bind futures that are already a path.
176-
// This prevents creating redundant stack space
177-
// for them.
178-
syn::Expr::Path(path) => path,
175+
syn::Expr::Path(path) => {
176+
// Don't bind futures that are already a path.
177+
// This prevents creating redundant stack space
178+
// for them.
179+
path
180+
},
179181
_ => {
182+
// Bind and pin the resulting Future on the stack. This is
183+
// necessary to support direct select! calls on !Unpin
184+
// Futures.
185+
// Safety: This is safe since the lifetime of the Future
186+
// is totally constraint to the lifetime of the select!
187+
// expression, and the Future can't get moved inside it
188+
// (it is shadowed).
180189
future_let_bindings.push(quote! {
181190
let mut #variant_name = #expr;
191+
let mut #variant_name = unsafe {
192+
::core::pin::Pin::new_unchecked(&mut #variant_name)
193+
};
182194
});
183195
parse_quote! { #variant_name }
184196
}

futures-util/src/async_await/select_mod.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,15 @@ macro_rules! document_select_macro {
88
($item:item) => {
99
/// Polls multiple futures and streams simultaneously, executing the branch
1010
/// for the future that finishes first. If multiple futures are ready,
11-
/// one will be pseudo-randomly selected at runtime. Futures passed to
12-
/// `select!` must be `Unpin` and implement `FusedFuture`.
11+
/// one will be pseudo-randomly selected at runtime. Futures directly
12+
/// passed to `select!` must be `Unpin` and implement `FusedFuture`.
13+
///
14+
/// If an expression which yields a `Future` is passed to `select!`
15+
/// (e.g. an `async fn` call) instead of a `Future` directly the `Unpin`
16+
/// requirement is relaxed, since the macro will pin the resulting `Future`
17+
/// on the stack. However the `Future` returned by the expression must
18+
/// still implement `FusedFuture`.
19+
///
1320
/// Futures and streams which are not already fused can be fused using the
1421
/// `.fuse()` method. Note, though, that fusing a future or stream directly
1522
/// in the call to `select!` will not be enough to prevent it from being

futures/tests/async_await_macros.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,25 @@ fn select_size() {
161161
assert_eq!(::std::mem::size_of_val(&fut), 40);
162162
}
163163

164+
#[test]
165+
fn select_on_non_unpin_expressions() {
166+
// The returned Future is !Unpin
167+
let make_non_unpin_fut = || { async {
168+
5
169+
}};
170+
171+
let res = block_on(async {
172+
let select_res;
173+
select! {
174+
value_1 = make_non_unpin_fut().fuse() => { select_res = value_1 },
175+
value_2 = make_non_unpin_fut().fuse() => { select_res = value_2 },
176+
default => { select_res = 7 },
177+
};
178+
select_res
179+
});
180+
assert_eq!(res, 5);
181+
}
182+
164183
#[test]
165184
fn join_size() {
166185
let fut = async {

0 commit comments

Comments
 (0)