dtolnay / async-trait

Type erasure for async trait methods
Apache License 2.0
1.84k stars 85 forks source link

Different behaviour between non-trait async implementation and async-trait #231

Closed gmalette closed 1 year ago

gmalette commented 1 year ago

Disclaimer: I'm quite new to Rust and I may be using some wrong terminology, please bear with me.

I'm trying to extract a new concept from an implementation into a trait and I'm getting a weird behaviour which I can't figure out. I managed to reduce it to this code.

use crate::abi::comet::Comet;
use ethers_providers::{Provider, Ws};

pub struct Concrete {
    contract: Comet<Provider<Ws>>,
}

#[async_trait::async_trait]
trait Foo {
    async fn get_foo(&self) -> Result<u8, ContractError<Provider<Ws>>>;
}

impl Concrete {
    async fn get_foo(&self) -> Result<u8, ContractError<Provider<Ws>>> {
        self.contract.num_assets().await
    }
}

#[async_trait::async_trait]
impl Foo for Concrete {
    async fn get_foo(&self) -> Result<u8, ContractError<Provider<Ws>>> {
        self.contract.num_assets().await
    }
}

Notice that both implementations of get_foo are identical. However, in the impl Foo for Concrete case, the code doesn't compile, giving this error:

error: future cannot be sent between threads safely
  --> src/compound3.rs:21:72
   |
21 |       async fn get_foo(&self) -> Result<u8, ContractError<Provider<Ws>>> {
   |  ________________________________________________________________________^
22 | |         self.contract.num_assets().await
23 | |     }
   | |_____^ future created by async block is not `Send`
   |
   = help: the trait `std::marker::Send` is not implemented for `dyn futures::Future<Output = std::result::Result<u8, ethers::contract::ContractError<ethers_providers::Provider<ethers_providers::Ws>>>>`
note: future is not `Send` as this value is used across an await
  --> src/compound3.rs:22:35
   |
22 |         self.contract.num_assets().await
   |                                   ^^^^^^
   |                                   |
   |                                   await occurs here, with `.await` maybe used later
   |                                   has type `std::pin::Pin<Box<dyn futures::Future<Output = std::result::Result<u8, ethers::contract::ContractError<ethers_providers::Provider<ethers_providers::Ws>>>>>>` which is not `Send`
23 |     }
   |     - `.await` is later dropped here
   = note: required for the cast from `impl futures::Future<Output = std::result::Result<u8, ethers::contract::ContractError<ethers_providers::Provider<ethers_providers::Ws>>>>` to the object type `dyn futures::Future<Output = std::result::Result<u8, ethers::contract::ContractError<ethers_providers::Provider<ethers_providers::Ws>>>> + std::marker::Send`

I would expect that, since the non-trait function compiles, this would mean that the Result is Send, and for that to extend to the trait function. Am I missing something?

This is using async-trait 0.1.62, Rust stable 1.66.0

If I switch to nightly, remove the async_trait annotations, and enable the #![feature(async_fn_in_trait)] feature, this code compiles without errors.

The Provider, Ws, and ContractError types are from https://docs.rs/ethers/latest/ethers/, with Comet being codegen from that crate, if that matters.

dtolnay commented 1 year ago

I am quite sure async-trait is behaving correctly as far as that code would be concerned. You can try taking this to any of the resources shown in https://www.rust-lang.org/community to get help making sense of what is going on in your specific case.

gmalette commented 1 year ago

@dtolnay thanks for looking at this so quickly! If async-trait is behaving correctly, does that mean that nightly isn't? Or is it more that async-trait has limitations that nightly doesn't have?