async_fn_in_trait
and return_type_notation
cause awkward awaits · Issue #112569 · rust-lang/rust (original) (raw)
This bug is not exclusively related to the unstable async_fn_in_trait
and return_type_notation
, but the ergonomics of these features is an important contributing factor.
I tried this code:
#![feature( async_fn_in_trait, return_type_notation, )]
use tokio::task::{JoinHandle, spawn};
trait Foo: Send { async fn bar(&self) -> i32; }
fn thing(factory: impl Foo<bar(): Send> + 'static) -> JoinHandle { spawn(async move { factory.bar().await }) }
And got this error message:
error: future cannot be sent between threads safely
--> src/lib.rs:13:11
|
13 | spawn(async move { factory.bar().await })
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future created by async block is not Send
|
note: future is not Send
as this value is used across an await
--> src/lib.rs:13:38
|
13 | spawn(async move { factory.bar().await })
| ------- ^^^^^ - factory
is later dropped here
| | |
| | await occurs here, with factory
maybe used later
| has type &impl Foo<bar() : Send> + 'static
which is not Send
help: consider moving this into a let
binding to create a shorter lived borrow
--> src/lib.rs:13:24
|
13 | spawn(async move { factory.bar().await })
| ^^^^^^^^^^^^^
note: required by a bound in tokio::spawn
--> /usr/local/google/home/dkoloski/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/task/spawn.rs:163:21
|
161 | pub fn spawn(future: T) -> JoinHandle<T::Output>
| ----- required by a bound in this function
162 | where
163 | T: Future + Send + 'static,
| ^^^^ required by this bound in spawn
help: consider further restricting this bound
|
12 | fn thing(factory: impl Foo<bar(): Send> + 'static + std:📑:Sync) -> JoinHandle {
| +++++++++++++++++++
First things first, this diagnostic is 100% correct. Making either of these changes fixes the issue. However, this is a very annoying problem that will probably get worse as these features stabilize.
The root of the issue is that the impl Foo<..> + 'static
parameter is opaque and so conservatively does not implement Sync
. That's the fix suggested second in the diagnostic. We really don't need it to impl Sync
so it doesn't make sense to do this.
The reason why it does need to implement Sync
is because a reference to it is being held across an await point. However, I didn't make this reference - my argument was auto-ref'd to call bar()
and that temporary is living until the end of the statement (and thus across an await point). I don't think this behavior makes sense, and so it may make sense to change it in the next edition.
You can reproduce this behavior without any nightly or unstable features with the following code:
use std::{cell::Cell, future::Future}; use tokio::task::{JoinHandle, spawn};
struct Foo(Cell);
impl Foo { fn bar(&self) -> impl Future<Output = i32> + '_ + Send { async { 10 } } }
fn thing(factory: Foo) -> JoinHandle { spawn(async move { factory.bar().await }) }
But it's not quite as interesting.
Meta
rustc --version --verbose
:
rustc 1.72.0-nightly (37998ab50 2023-06-11)
binary: rustc
commit-hash: 37998ab508d5d9fa0d465d7b535dc673087dda8f
commit-date: 2023-06-11
host: x86_64-unknown-linux-gnu
release: 1.72.0-nightly
LLVM version: 16.0.5