Skip to content

async_fn_in_trait and return_type_notation cause awkward awaits #112569

Closed
@djkoloski

Description

@djkoloski

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<i32> {
    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<T>(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::marker::Sync) -> JoinHandle<i32> {
    |                                                   +++++++++++++++++++

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<i32>);

impl Foo {
    fn bar(&self) -> impl Future<Output = i32> + '_ + Send {
        async { 10 }
    }
}

fn thing(factory: Foo) -> JoinHandle<i32> {
    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-async-awaitArea: Async & AwaitAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.C-bugCategory: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.WG-asyncWorking group: Async & await

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions