Further, I need to store "hello" function in a wrapper, then I can use it multiple times. for example
pub struct RpcRetryWrapper<C, I, O, F> {
f: F,
marker: std::marker::PhantomData<fn(&mut C, I) -> O>,
}
impl<C, I, O, F> RpcRetryWrapper
where
F: // trait bound
{
async fn call_once(&mut self, client: &mut C, request: I) -> O {
// do something
}
async fn call_twice(&mut self, client: &mut C, request: I) -> O
where
I: Clone
{
let first_res = self.call_once(client, request.clone());
let second_res = self.call_once(client, request);
// do something
}
}
However, there is a question that I can not find correct trait bound of F. I guess the trait bound is something like this, but compiler will report an error.
Just treat O as the future type and use <O as Future>::Output to get the final output of the async function.This may not fix all of your problems, but it does fix the immediate errors in your example
use std::future::Future;
pub struct RpcRetryWrapper<C, I, O, F> {
f: F,
marker: std::marker::PhantomData<fn(&mut C, I) -> O>,
}
impl<C, I, O, F> RpcRetryWrapper<C, I, O, F>
where
F: for<'a> Fn(&'a mut C, I) -> O,
O: Future,
{
async fn call_once(&mut self, client: &mut C, request: I) -> O::Output {
// do something
todo!()
}
async fn call_twice(&mut self, client: &mut C, request: I) -> O::Output
where
I: Clone,
{
let first_res = self.call_once(client, request.clone()).await; // await necessary to avoid a borrow checker error
let second_res = self.call_once(client, request);
// do something
todo!()
}
}
impl<C, I, O, F> RpcRetryWrapper<C, I, F>
where
I: Clone,
F: for<'a> Fn(&'a mut C, I) -> O,
O: Future,
async fn outputs capture their input lifetimes, and so they cannot be represented by a type parameter like O when there is an input lifetime, as O must resolve to a single concrete type. The returned Futures are not all the same single type; they vary by lifetime.
construct a concrete type LifetimeBoundFutureWrapper for output future of async fn which capture input lifetimes. it seems ok to convert output of call_example into a LifetimeBoundFutureWrapper<'_, String, _>.
trait LifetimeBoundFuture<'a, T>: 'a + Future<Output = T> {}
impl<'a, T, F> LifetimeBoundFuture<'a, T> for F where F: 'a + Future<Output = T> {}
struct LifetimeBoundFutureWrapper<'a, T, F> {
fut: F,
marker: std::marker::PhantomData<fn() -> &'a T>,
}
impl<'a, T, F> From<F> for LifetimeBoundFutureWrapper<'a, T, F>
where
F: LifetimeBoundFuture<'a, T>,
{
fn from(fut: F) -> Self {
Self {
fut,
marker: std::marker::PhantomData,
}
}
}
async fn call_exmaple(client: &mut String, request: String) -> String {
request
}
#[cfg(test)]
mod tests {
use crate::{call_exmaple, LifetimeBoundFutureWrapper, RpcRetryWrapper};
#[tokio::test]
async fn future_wrapper_test() {
let mut client = "client".to_string();
let request = "hello".to_string();
let fut_wrapper: LifetimeBoundFutureWrapper<'_, String, _> =
call_exmaple(&mut client, request).into();
}
}
Modify trait bound in RpcRetryWrapper
impl<C, I, O, F> RpcRetryWrapper<C, I, F>
where
I: Clone,
F: for<'a> Fn(&'a mut C, I) -> LifetimeBoundFutureWrapper<'a, O::Output, O>,
O: Future,
Finally, how to prove that call_example satisfy the trait bound. All i knew is to define an immediate trait like AsyncFuncExt to do blanket implementation for all async fn of this form.