Home > Software engineering >  Using !Send object on async function works, but does not work on trait function
Using !Send object on async function works, but does not work on trait function

Time:07-25

Suppose we have a type that is not Send.

struct NotSend {
    field: std::rc::Rc<i32>
}

Then, following async function can still take NotSend as its parameter and compiles well:

async fn func(not_send: NotSend) -> i32 {
    0
}

But when I define the same function inside of the trait, then anything that implements it does not compile.

#[async_trait]
trait A {
    async fn func(not_send: NotSend) -> i32;
}

struct S {
    
}

#[async_trait]
impl A for S {
    async fn func(not_send: NotSend) -> i32 {
        0
    }
}

This fails with the following message:

error: future cannot be sent between threads safely
  --> src/main.rs:23:46
   |
23 |       async fn func( not_send: NotSend) -> i32 {
   |  ______________________________________________^
24 | |         0
25 | |     }
   | |_____^ future created by async block is not `Send`
   |
   = help: within `impl Future<Output = i32>`, the trait `Send` is not implemented for `Rc<i32>`
note: captured value is not `Send`
  --> src/main.rs:23:20
   |
23 |     async fn func( not_send: NotSend) -> i32 {
   |                    ^^^^^^^^ has type `NotSend` which is not `Send`
   = note: required for the cast to the object type `dyn Future<Output = i32>   Send`

What's so different from the naive function and the function in the trait? Why one does works but not the other? Playground Link

CodePudding user response:

It's because async_trait expands to something like Pin<Box<dyn Future>>. If we want the resulting future to be Send, it needs to be Pin<Box<dyn Future Send>>. But this forces it to be Send, i.e. will error on non-Send futures. The async_trait crate does not have a way to know whether the future is Send (because the implementation of the trait is different from its declaration, and we need to decide at declaration site whether the future will be Send or not), so it opts to use user-defined annotations.

By default, the generated type is Send since most futures needs to be Send. However, as explained in the documentation, you can use #[async_trait(?Send)] to opt this out:

#[async_trait(?Send)]
trait A {
    async fn func(not_send: NotSend) -> i32;
}

struct S {
    
}

#[async_trait(?Send)]
impl A for S {
    async fn func(not_send: NotSend) -> i32 {
        0
    }
}
  • Related