Skip to content

Allow !Unpin Futures in select! #1873

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions futures-select-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,25 @@ pub fn select(input: TokenStream) -> TokenStream {
.zip(variant_names.iter())
.map(|(expr, variant_name)| {
match expr {
// Don't bind futures that are already a path.
// This prevents creating redundant stack space
// for them.
syn::Expr::Path(path) => path,
syn::Expr::Path(path) => {
// Don't bind futures that are already a path.
// This prevents creating redundant stack space
// for them.
path
},
_ => {
// Bind and pin the resulting Future on the stack. This is
// necessary to support direct select! calls on !Unpin
// Futures.
// Safety: This is safe since the lifetime of the Future
// is totally constraint to the lifetime of the select!
// expression, and the Future can't get moved inside it
// (it is shadowed).
future_let_bindings.push(quote! {
let mut #variant_name = #expr;
let mut #variant_name = unsafe {
::core::pin::Pin::new_unchecked(&mut #variant_name)
};
});
parse_quote! { #variant_name }
}
Expand Down
11 changes: 9 additions & 2 deletions futures-util/src/async_await/select_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@ macro_rules! document_select_macro {
($item:item) => {
/// Polls multiple futures and streams simultaneously, executing the branch
/// for the future that finishes first. If multiple futures are ready,
/// one will be pseudo-randomly selected at runtime. Futures passed to
/// `select!` must be `Unpin` and implement `FusedFuture`.
/// one will be pseudo-randomly selected at runtime. Futures directly
/// passed to `select!` must be `Unpin` and implement `FusedFuture`.
///
/// If an expression which yields a `Future` is passed to `select!`
/// (e.g. an `async fn` call) instead of a `Future` directly the `Unpin`
/// requirement is relaxed, since the macro will pin the resulting `Future`
/// on the stack. However the `Future` returned by the expression must
/// still implement `FusedFuture`.
///
/// Futures and streams which are not already fused can be fused using the
/// `.fuse()` method. Note, though, that fusing a future or stream directly
/// in the call to `select!` will not be enough to prevent it from being
Expand Down
19 changes: 19 additions & 0 deletions futures/tests/async_await_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,25 @@ fn select_size() {
assert_eq!(::std::mem::size_of_val(&fut), 40);
}

#[test]
fn select_on_non_unpin_expressions() {
// The returned Future is !Unpin
let make_non_unpin_fut = || { async {
5
}};

let res = block_on(async {
let select_res;
select! {
value_1 = make_non_unpin_fut().fuse() => { select_res = value_1 },
value_2 = make_non_unpin_fut().fuse() => { select_res = value_2 },
default => { select_res = 7 },
};
select_res
});
assert_eq!(res, 5);
}

#[test]
fn join_size() {
let fut = async {
Expand Down