hobofan / ambassador

Delegation of trait implementations via procedural macros
Apache License 2.0
251 stars 13 forks source link

Help delegating to tokio::sync::RwLock guard #49

Open cameronbraid opened 1 year ago

cameronbraid commented 1 year ago

I need some help in using Ambassador to generate a delegate like my following hand crafted delegate implementation.

I see in issue 20 that what I am attempting to do may be supported. I was hoping that this pull (which issue 20 references) would allow me to do delegation like the following (similar to example in https://github.com/hobofan/ambassador/issues/20)


#[async_trait]
#[delegatable_trait]
pub trait ProductDaoDelegate {
    async fn get_for_id(&self, id: i64) -> Result<Option<ProductModel>>;
}

// struct I want to delegate to, `MockProductDaoImpl` implements `ProductDaoDelegate` trait
#[derive(Debug)]
pub struct RwLockMockProductDaoImpl(tokio::sync::RwLock<MockProductDaoImpl>);

// hand written delegate that I want to replace with Ambassador
#[async_trait]
impl ProductDaoDelegate for RwLockMockProductDaoImpl {
    async fn get_for_id(&self, id: i64) -> Result<Option<ProductModel>> {
        let guard = self.0.read().await;
        guard.get_for_id(id).await
    }
}

However I am unable to work it out.

Is someone able to given an example of delegating to a new type that wraps a tokio::sync::RwLock

dewert99 commented 8 months ago

Sorry for the slow response.

This example is challenging for a number of reasons:

  1. ambassador previously didn't support async trait functions, but this was fixed in #53
  2. The method you want to delegate to RwLock::read returns an impl Future<Output=RwLockReadGuard<'_, MockProductDaoImpl> rather that a &MockProductDaoImpl as would be expected by delegate_to_method. This can be avoided by using the underlying macro produced by #[delegatable_trait] directly as disscussed in #32. For you example this would look like:
    impl ProductDaoDelegate for RwLockMockProductDaoImpl {
    ambassador_impl_ProductDaoDelegate!{
        body_struct(<>, // Refers to the trait generics used
            MockProductDaoImpl, // Refers to the type being delegated to
            (), // Refers to the method used for `self` receiver
            // (`()` indicates no method exists and causes a compile error if any methods take this kind of receiver
            (0.read().await), // Refers to the method used for `&self` receivers
            () // Refers to the method used for `&mut self` receivers
        )
    }
    }
  3. Rust does not support eager macro expansion so ambassador doesn't work with async_trait (ambassador can't see the macro expansion done by async_trait and vice-versa`)
BTOdell commented 8 months ago

Just ran into the issue with ambassador not working with async_trait :(

Would a solution work where ambassador added macros to the impl block (that I assume it generates)? Something like this:

#[derive(Delegate)]
#[delegate(MyAsyncTrait, with_attrs = "async_trait")]
pub struct MyDerivedObject {
    base: MyBaseObject,
}

When ambassador generates the impl block, it could add the macros automatically:


#[async_trait]
impl MyAsyncTrait for MyDerivedObject {
    ...
}
dewert99 commented 8 months ago

The issue with this is that while ambassador could generate:

#[async_trait]
impl MyAsyncTrait for MyDerivedObject {
    ambassador_impl_MyAsyncTrait!{...}
}

(where ambassador_impl_MyAsyncTrait is a macro that gets generated by delegateable_trait), the async_trait macro would evaluate first and ignore ambassador_impl_MyAsyncTrait! since it is not an async fn, then ambassador_impl_MyAsyncTrait would expand into an async fn, but at this point it would be too late.

dewert99 commented 8 months ago

AFIT (Async function in traits) is already stable, and I'm hoping that once RTN (return type notation) and TAITIT (type alias impl trait in trait) are stabilized async_trait will become less important.