Async method that takes async closure in async trait

My goal is to define async method that takes async closure in async trait, in an attempt to wrap async DB operations in a transaction.

To do so I defined a trait Transaction with a method execute that takes closure op that returns Future, using async_trait create.
I also have a struct Usecase with transaction field holding a reference to a trait object that implements the Transaction trait.

#[async_trait]
pub trait Transaction {
    async fn execute<F, Fut>(&self, op: F) -> Result<()> where F: (FnOnce() -> Fut) + Send, Fut: Future<Output=Result<()>>;
}

pub struct Usecase {
    transaction: Arc<dyn Transaction>,
}

But when I tried to compile, it failed with the following error.
I followed the link in the error and knew that the trait is not object safe because execute method has type parameters.

error[E0038]: the trait `Transaction` cannot be made into an object
  --> src/app/sync_usercase.rs:19:22
   |
19 |     transaction: Arc<dyn Transaction>,
   |                      ^^^^^^^^^^^^^^^ `Transaction` cannot be made into an object

I don't have much experience in Rust and my knowledge is shallow so could you tell me
(1) if I am doing correct to achieve my goal?
(2) if so how can I fix the error, if not what are the correct directions?

To erase type parameters with trait bounds, the usual way is via dyn Trait. Rust Playground

#[async_trait]
pub trait Transaction {
    async fn execute(&self, op: Op<'_>) -> Result<()>;
}
type Op<'a> = Box<dyn 'a + Send + FnOnce() -> BoxFuture<'a, Result<()>>>;
pub struct Usecase {
    transaction: Arc<dyn Transaction>,
}

Box<dyn 'a + Send + FnOnce() ... can be replaced with &'a dyn Send + FnOnce() ... though.

Update: use Op<'_> instead of Op in #[async_trait] due to the macro usage caveat in async_trait - Rust

3 Likes

Are you sure? Maybe if it was an Fn, but you can't call a FnOnce with that.

3 Likes

Thanks for pointing that out.

It won't work for &dyn FnOnce Rust Playground .

1 Like

Thank you, it worked.
I will try to figure out myself why BoxFuture needs to be used instead of something like Box<dyn Future<Output=Result<()>>>.

In that case, the code won't compile: Rust Playground

error[E0277]: `dyn Future<Output = Result<(), anyhow::Error>>` cannot be unpinned
  --> src/main.rs:31:13
   |
31 |         op().await;
   |         ----^^^^^^
   |         |   |
   |         |   the trait `Unpin` is not implemented for `dyn Future<Output = Result<(), anyhow::Error>>`
   |         |   help: remove the `.await`
   |         this call returns `dyn Future<Output = Result<(), anyhow::Error>>`
   |
   = note: consider using the `pin!` macro
           consider using `Box::pin` if you need to access the pinned value outside of the current scope
   = note: required for `Box<dyn Future<Output = Result<(), anyhow::Error>>>` to implement `Future`
   = note: required for `Box<dyn Future<Output = Result<(), anyhow::Error>>>` to implement `IntoFuture`

Refer to Pinning - Asynchronous Programming in Rust

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.