Error complains about missing Sync, but Send is what's missing

If I try to compile the following:

use async_trait::async_trait;

#[async_trait]
trait T: Sync {
    async fn foo(&mut self) -> u32;
}

#[async_trait]
trait TInterface {
    async fn foo(&mut self) -> u32;
}

#[async_trait]
impl<Q: T + Sync> TInterface for Q {
    async fn foo(&mut self) -> u32 {
        let x = <Q as T>::foo(self).await;
        x
    }
}

I get the error;

   Compiling playground v0.1.0 (/home/prophet/playground)
error: future cannot be sent between threads safely
  --> src/lib.rs:15:36
   |
15 |       async fn foo(&mut self) -> u32 {
   |  ____________________________________^
16 | |         let x = <Q as T>::foo(self).await;
17 | |         x
18 | |     }
   | |_____^ future created by async block is not `Send`
   |
note: captured value is not `Send` because `&` references cannot be sent unless their referent is `Sync`
  --> src/lib.rs:15:23
   |
15 |     async fn foo(&mut self) -> u32 {
   |                       ^^^^ has type `&mut Q` which is not `Send`, because `Q` is not `Sync`
   = note: required for the cast from `impl Future<Output = u32>` to the object type `dyn Future<Output = u32> + Send`
help: consider further restricting this bound
   |
14 | impl<Q: T + Sync + std::marker::Send> TInterface for Q {
   |                  +++++++++++++++++++

Two things stick out a strange:

  1. The error complains Sync is missing, Q has a Sync bound, and in fact the trait T that also bounds Q also extends Sync. Q is definitely Sync, so this seems like a rustc bug? Unless somehow the magic async_trait is doing causes this?

  2. The suggested code change is allegedly to fix Sync being missing, but if you look at the actual change it adds a Send bound.

Yep, the problem is caused by async-trait, which requires that the future type be Send unless you write #[async_trait(?Send)]. Indeed, writing that for TInterface and TInterface for Q allows the snippet to compile (Rust Playground):

use async_trait::async_trait;

#[async_trait]
trait T: Sync {
    async fn foo(&mut self) -> u32;
}

#[async_trait(?Send)]
trait TInterface {
    async fn foo(&mut self) -> u32;
}

#[async_trait(?Send)]
impl<Q: T + Sync> TInterface for Q {
    async fn foo(&mut self) -> u32 {
        let x = <Q as T>::foo(self).await;
        x
    }
}

That diagnostic does look pretty weird, though; I'll look into it.

EDIT: I've checked the compiler, and the bug seems to be that it does not distinguish between captured &T and &mut T values before making the T: Sync suggestion:

The ty::Ref(_, ref_ty, _) pattern matches both &T and &mut T. A trivial solution would be to require hir::Mutability::Not, to restrict it to &T; I'll look into creating a PR.

EDIT 2: I have filed this as PR 105839.

5 Likes