dtolnay / async-trait

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

Returned `Self` expanded to `AsyncTrait`? #138

Closed grantperry closed 3 years ago

grantperry commented 3 years ago

G'Day!

I'm having an issue using futures::StreamExt and friends. I think the issue I have is due to the expanded macro having

Context

I'm deserialising objects from a DB. The method in my example returns a collection of itself Vec<Self> The macro expansion seems to turn this into Vec<AsyncTrait>. I included the error at the bottom even though I don't think its quite relevant. What is more relevant is the macro expansion.

Code

#[async_trait]
pub trait Db<'a>: Send + Sized + Deserialize<'static> + 'static {
    const COLLECTION: &'static str;

    async fn all(db: &'a Database) -> Vec<Self> {
        db.collection(Self::COLLECTION)
            .find(None, None)
            .await
            .unwrap()
            .filter_map(|item| async move { item.ok() })
            .map(|doc| async move { bson::from_document(doc).expect("Decode error") })
            .collect()
            .await
    }
}

Expanded macro

This is where things don't go as I would expect. The outer signature looks fine but the async fn defined inside has the return signature of Vec<AsyncTrait> which is my problem.

    fn all<'async_trait>(
        db: &'a Database,
    ) -> ::core::pin::Pin<
        Box<dyn ::core::future::Future<Output = Vec<Self>> + ::core::marker::Send + 'async_trait>,
    >
    where
        'a: 'async_trait,
        Self: 'async_trait,
    {
        // #[allow ... ]
        async fn __all<'a, AsyncTrait: ?Sized + Db<'a>>(db: &'a Database) -> Vec<AsyncTrait> { // Why was `Self` changed to `AsyncTrait`?
            db.collection(<AsyncTrait>::COLLECTION)
                .find(None, None)
                .await
                .unwrap()
                .filter_map(|item| async move { item.ok() })
                .map(|doc| async move { bson::from_document(doc).expect("Decode error") })
                .collect()
                .await
        }
        Box::pin(__all::<Self>(db))
    }

Source

I'm going to make a wild accusation and say that this behaviour comes from here but I don't know much about macros and TokenStreams and such. This looks like it might replace Self with AsyncTrait

Error

error[E0277]: the trait bound `Vec<AsyncTrait>: Extend<impl futures::Future>` is not satisfied
  --> mmt-db/src/lib.rs:16:9
   |
16 | /         db.collection(Self::COLLECTION)
17 | |             .find(None, None)
18 | |             .await
19 | |             .unwrap()
...  |
22 | |             .collect()
23 | |             .await
   | |__________________^ the trait `Extend<impl futures::Future>` is not implemented for `Vec<AsyncTrait>`
   |
   = help: the following implementations were found:
             <Vec<T, A> as Extend<&'a T>>
             <Vec<T, A> as Extend<T>>
   = note: required because of the requirements on the impl of `futures::Future` for `Collect<futures::stream::Map<futures::stream::FilterMap<mongodb::Cursor, impl futures::Future, [closure@mmt-db/src/lib.rs:20:25: 20:56]>, [closure@mmt-db/src/lib.rs:21:18: 21:86]>, Vec<AsyncTrait>>`
   = note: required by `futures::Future::poll`

Hopefully I've just made a silly mistake and you can point that out. Thanks for this awesome crate!

Grant

grantperry commented 3 years ago

Hmmm. Maybe I've made a bit too much noise without thinking. I'm finally understanding how the macro works. Its supertrait is Db which makes more sense.

The real question I have is what does my error actually mean? I'm going to sleep on this. My original comment seems invalid now?

dtolnay commented 3 years ago

The error looks correct to me. You are trying to collect a stream of ?? into a Vec<Self> where ?? is whatever anonymous future type the async move block in map evaluates to, and is definitely not the same type as Self. Probably you didn't mean to put an async block there.

grantperry commented 3 years ago

Thanks. Yep you were right. Barking up the wrong tree...