Skip to content

Commit 2137a2e

Browse files
Matthias247cramertj
authored andcommitted
Improve support for mutable borrows in select! blocks
As reported in #1811 mutable accesses were not allowed inside select! blocks if a Future inside the same expression borrowed it. This is not necessarily required, since the actual polling of the Futures occurs before the result blocks are evaluated. This change allows borrowing a variable inside a Future and referring to the same variable inside other select branches. The change introduces additional scoping into the select macro which separates the future poll phase from executing the result blocks phase. Fixes #1811
1 parent 4a49fbf commit 2137a2e

File tree

2 files changed

+126
-37
lines changed

2 files changed

+126
-37
lines changed

futures-select-macro/src/lib.rs

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -250,58 +250,74 @@ pub fn select(input: TokenStream) -> TokenStream {
250250
#complete_branch
251251
};
252252

253-
let await_and_select = if let Some(default_expr) = parsed.default {
253+
let await_select_fut = if parsed.default.is_some() {
254+
// For select! with default this returns the Poll result
254255
quote! {
255-
if let #futures_crate::task::Poll::Ready(x) =
256-
__poll_fn(&mut #futures_crate::task::Context::from_waker(
257-
#futures_crate::task::noop_waker_ref()
258-
))
259-
{
260-
match x { #branches }
261-
} else {
262-
#default_expr
263-
};
256+
__poll_fn(&mut #futures_crate::task::Context::from_waker(
257+
#futures_crate::task::noop_waker_ref()
258+
))
259+
}
260+
} else {
261+
quote! {
262+
#futures_crate::future::poll_fn(__poll_fn).await
263+
}
264+
};
265+
266+
let execute_result_expr = if let Some(default_expr) = &parsed.default {
267+
// For select! with default __select_result is a Poll, otherwise not
268+
quote! {
269+
match __select_result {
270+
#futures_crate::task::Poll::Ready(result) => match result {
271+
#branches
272+
},
273+
_ => #default_expr
274+
}
264275
}
265276
} else {
266277
quote! {
267-
match #futures_crate::future::poll_fn(__poll_fn).await {
278+
match __select_result {
268279
#branches
269280
}
270281
}
271282
};
272283

273284
TokenStream::from(quote! { {
274285
#enum_item
275-
#( #future_let_bindings )*
276-
277-
let mut __poll_fn = |__cx: &mut #futures_crate::task::Context<'_>| {
278-
let mut __any_polled = false;
279-
280-
#( #poll_functions )*
281286

282-
let mut __select_arr = [#( #variant_names ),*];
283-
#futures_crate::async_await::shuffle(&mut __select_arr);
284-
for poller in &mut __select_arr {
285-
let poller: &mut &mut dyn FnMut(
286-
&mut #futures_crate::task::Context<'_>
287-
) -> Option<#futures_crate::task::Poll<_>> = poller;
288-
match poller(__cx) {
289-
Some(x @ #futures_crate::task::Poll::Ready(_)) =>
290-
return x,
291-
Some(#futures_crate::task::Poll::Pending) => {
292-
__any_polled = true;
287+
let __select_result = {
288+
#( #future_let_bindings )*
289+
290+
let mut __poll_fn = |__cx: &mut #futures_crate::task::Context<'_>| {
291+
let mut __any_polled = false;
292+
293+
#( #poll_functions )*
294+
295+
let mut __select_arr = [#( #variant_names ),*];
296+
#futures_crate::async_await::shuffle(&mut __select_arr);
297+
for poller in &mut __select_arr {
298+
let poller: &mut &mut dyn FnMut(
299+
&mut #futures_crate::task::Context<'_>
300+
) -> Option<#futures_crate::task::Poll<_>> = poller;
301+
match poller(__cx) {
302+
Some(x @ #futures_crate::task::Poll::Ready(_)) =>
303+
return x,
304+
Some(#futures_crate::task::Poll::Pending) => {
305+
__any_polled = true;
306+
}
307+
None => {}
293308
}
294-
None => {}
295309
}
296-
}
297310

298-
if !__any_polled {
299-
#none_polled
300-
} else {
301-
#futures_crate::task::Poll::Pending
302-
}
311+
if !__any_polled {
312+
#none_polled
313+
} else {
314+
#futures_crate::task::Poll::Pending
315+
}
316+
};
317+
318+
#await_select_fut
303319
};
304320

305-
#await_and_select
321+
#execute_result_expr
306322
} })
307323
}

futures/tests/async_await_macros.rs

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
use futures::{Poll, pending, pin_mut, poll, join, try_join, select};
44
use futures::channel::{mpsc, oneshot};
55
use futures::executor::block_on;
6-
use futures::future::{self, FutureExt};
6+
use futures::future::{self, FutureExt, poll_fn};
77
use futures::stream::StreamExt;
88
use futures::sink::SinkExt;
9+
use futures::task::Context;
910

1011
#[test]
1112
fn poll_and_pending() {
@@ -180,6 +181,78 @@ fn select_on_non_unpin_expressions() {
180181
assert_eq!(res, 5);
181182
}
182183

184+
#[test]
185+
fn select_can_be_used_as_expression() {
186+
block_on(async {
187+
let res = select! {
188+
x = future::ready(7) => { x },
189+
y = future::ready(3) => { y + 1 },
190+
};
191+
assert!(res == 7 || res == 4);
192+
});
193+
}
194+
195+
#[test]
196+
fn select_with_default_can_be_used_as_expression() {
197+
fn poll_always_pending<T>(_cx: &mut Context<'_>) -> Poll<T> {
198+
Poll::Pending
199+
}
200+
201+
block_on(async {
202+
let res = select! {
203+
x = poll_fn(poll_always_pending::<i32>).fuse() => x,
204+
y = poll_fn(poll_always_pending::<i32>).fuse() => { y + 1 },
205+
default => 99,
206+
};
207+
assert_eq!(res, 99);
208+
});
209+
}
210+
211+
#[test]
212+
fn select_with_complete_can_be_used_as_expression() {
213+
block_on(async {
214+
let res = select! {
215+
x = future::pending::<i32>() => { x },
216+
y = future::pending::<i32>() => { y + 1 },
217+
default => 99,
218+
complete => 237,
219+
};
220+
assert_eq!(res, 237);
221+
});
222+
}
223+
224+
async fn require_mutable(_: &mut i32) {}
225+
async fn async_noop() {}
226+
227+
#[test]
228+
fn select_on_mutable_borrowing_future_with_same_borrow_in_block() {
229+
block_on(async {
230+
let mut foo = 234;
231+
select! {
232+
x = require_mutable(&mut foo).fuse() => { },
233+
y = async_noop().fuse() => {
234+
foo += 5;
235+
},
236+
}
237+
});
238+
}
239+
240+
#[test]
241+
fn select_on_mutable_borrowing_future_with_same_borrow_in_block_and_default() {
242+
block_on(async {
243+
let mut foo = 234;
244+
select! {
245+
x = require_mutable(&mut foo).fuse() => { },
246+
y = async_noop().fuse() => {
247+
foo += 5;
248+
},
249+
default => {
250+
foo += 27;
251+
},
252+
}
253+
});
254+
}
255+
183256
#[test]
184257
fn join_size() {
185258
let fut = async {

0 commit comments

Comments
 (0)