dtolnay / async-trait

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

Strange compiler error with async_trait and dashmap #141

Closed kaimast closed 3 years ago

kaimast commented 3 years ago

Hi,

I am trying to store an async trait in a dashmap and get a very cryptic error message. The example below compiles fine with a regular trait but fails to compile with async_trait.

I already filed a bug with dashmap (https://github.com/xacrimon/dashmap/issues/131) but the author is somewhat clueless to what the cause of this could be.

Any help is highly appreciated!

use async_trait::async_trait;

use std::sync::Arc;
use dashmap::DashMap;

#[ async_trait ]
trait MyTrait: Send+Sync {
    async fn func(&self);
}

type MapType = DashMap<u64, Arc<dyn MyTrait>>;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mymap = Arc::new( MapType::new() );

    let fut = tokio::spawn(async move {
        for e in mymap.iter() {
            e.value().func().await;
        }
    });

    fut.await.unwrap();
    Ok(())
}

The error message I get is the following:

   Compiling dashmap-tokio v0.1.0 (/home/kai/dev/dashmap-tokio)
error: implementation of `dashmap::Map` is not general enough
  --> src/main.rs:17:15
   |
17 |       let fut = tokio::spawn(async move {
   |                 ^^^^^^^^^^^^ implementation of `dashmap::Map` is not general enough
   |
  ::: /home/kai/.cargo/registry/src/github.com-1ecc6299db9ec823/dashmap-4.0.2/src/t.rs:12:1
   |
12 | / pub trait Map<'a, K: 'a + Eq + Hash, V: 'a, S: 'a + Clone + BuildHasher> {
13 | |     fn _shard_count(&self) -> usize;
14 | |
15 | |     /// # Safety
...  |
94 | |     }
95 | | }
   | |_- trait `dashmap::Map` defined here
   |
   = note: `DashMap<u64, Arc<(dyn MyTrait + '0)>>` must implement `dashmap::Map<'_, u64, Arc<(dyn MyTrait + '1)>, RandomState>`, for any two lifetimes `'0` and `'1`...
   = note: ...but `dashmap::Map<'_, u64, Arc<dyn MyTrait>, RandomState>` is actually implemented for the type `DashMap<u64, Arc<dyn MyTrait>>`

error: aborting due to previous error

error: could not compile `dashmap-tokio`

To learn more, run the command again with --verbose.
dtolnay commented 3 years ago

This is a rustc bug, and not related to async-trait. You can work around by putting a wrapper struct between the trait object and DashMap.

@@ -7,7 +7,8 @@ trait MyTrait: Send + Sync {
     async fn func(&self);
 }

-type MapType = DashMap<u64, Arc<dyn MyTrait>>;
+struct ArcMyTrait(Arc<dyn MyTrait>);
+type MapType = DashMap<u64, ArcMyTrait>;

 #[tokio::main]
 async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -15,7 +16,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

     let fut = tokio::spawn(async move {
         for e in mymap.iter() {
-            e.value().func().await;
+            e.value().0.func().await;
         }
     });

https://github.com/rust-lang/rust/issues/64552

thombles commented 3 years ago

For the benefit of anyone searching, I think I've hit the same thing with a rather different error, after upgrading from async-trait 0.1.42 (works fine) to 0.1.43+ (has error below).

I can confirm that the above suggestion of a wrapper type around the Box works in this case also.

Minimal example:

#[async_trait]
trait AsyncTrait {
    async fn trait_fn() -> Result<Box<dyn Deref<Target = [u8]>>, anyhow::Error>;
}

struct S;
#[async_trait]
impl AsyncTrait for S {
    async fn trait_fn() -> Result<Box<dyn Deref<Target = [u8]>>, anyhow::Error> {
        let _f = std::fs::File::open("/tmp/foo")?;
        return Ok(Box::new(&b""[..]));
    }
}

Errors:

error[E0308]: mismatched types
  --> src/main.rs:12:81
   |
12 |       async fn trait_fn() -> Result<Box<dyn Deref<Target = [u8]>>, anyhow::Error> {
   |  _________________________________________________________________________________^
13 | |         let _f = std::fs::File::open("/tmp/foo")?;
14 | |         return Ok(Box::new(&b""[..]));
15 | |     }
   | |_____^ expected `&[u8]`, found trait object `dyn std::ops::Deref`
   |
   = note: expected type `std::result::Result<std::boxed::Box<&[u8]>, _>`
              found enum `std::result::Result<std::boxed::Box<dyn std::ops::Deref<Target = [u8]>>, anyhow::Error>`

error[E0271]: type mismatch resolving `<impl std::future::Future as std::future::Future>::Output == std::result::Result<std::boxed::Box<(dyn std::ops::Deref<Target = [u8]> + 'static)>, anyhow::Error>`
  --> src/main.rs:12:81
   |
12 |       async fn trait_fn() -> Result<Box<dyn Deref<Target = [u8]>>, anyhow::Error> {
   |  _________________________________________________________________________________^
13 | |         let _f = std::fs::File::open("/tmp/foo")?;
14 | |         return Ok(Box::new(&b""[..]));
15 | |     }
   | |_____^ expected trait object `dyn std::ops::Deref`, found `&[u8]`
   |
   = note: expected enum `std::result::Result<std::boxed::Box<(dyn std::ops::Deref<Target = [u8]> + 'static)>, anyhow::Error>`
              found enum `std::result::Result<std::boxed::Box<&[u8]>, _>`
   = note: required for the cast to the object type `dyn std::future::Future<Output = std::result::Result<std::boxed::Box<(dyn std::ops::Deref<Target = [u8]> + 'static)>, anyhow::Error>> + std::marker::Send`

error: aborting due to 2 previous errors