Lifetime error with generic function calling a trait method which takes a reference as argument

I'm having some troubles understanding the lifetime issue with the following code:

use std::future::Future;

use async_trait::async_trait;

#[derive(Debug)]
enum CalcError {
    DeserializerError(serde_json::Error),
    SerializerError(serde_json::Error),
    DivisionByZero
}

#[derive(Debug, serde::Deserialize)]
struct CalcRequest {
    a: i64,
    b: i64,
}

#[derive(Debug, serde::Serialize)]
struct CalcResponse {
    result: i64,
}

#[async_trait]
trait Calc {
    async fn div(&self, request: &CalcRequest) -> Result<CalcResponse, CalcError>;
}

#[derive(Default)]
struct Server {}

#[async_trait]
impl Calc for Server {
    async fn div(&self, request: &CalcRequest) -> Result<CalcResponse, CalcError> {
        if request.b != 0 {
            Ok(CalcResponse { result: request.a / request.b })
        } else {
            Err(CalcError::DivisionByZero)
        }
    }
}

#[inline]
async fn call_provider_method<'a, Service, Method, Input, Output, Fut>(
    service: &Service,
    f: Method,
    input: &'a [u8],
) -> Result<Vec<u8>, CalcError>
where
    Method: Fn(&Service, &Input) -> Fut,
    Input: serde::Deserialize<'a>,
    Output: serde::Serialize,
    Fut: Future<Output = Result<Output, CalcError>>,
{
    let request = serde_json::from_slice(input).map_err(|e| CalcError::DeserializerError(e))?;
    let output = f(service, &request).await?;
    let response = serde_json::to_vec(&output).map_err(|e| CalcError::SerializerError(e))?;
    Ok(response)
}

#[tokio::main]
async fn main() {
    let server = Server::default();
    let input = "{\"a\":84,\"b\":2}";
    let result = call_provider_method(&server, Calc::div, input.as_bytes()).await;
    assert_eq!(result.unwrap(), "{\"result\":42}".as_bytes());
}

It gives me the following error:

error[E0631]: type mismatch in function arguments
  --> src/main.rs:65:18
   |
26 |     async fn div(&self, request: &CalcRequest) -> Result<CalcResponse, CalcError>;
   |     ------------------------------------------------------------------------------ found signature of `fn(&_, &CalcRequest) -> _`
...
44 | async fn call_provider_method<'a, Service, Method, Input, Output, Fut>(
   |          -------------------- required by a bound in this
...
50 |     Method: Fn(&Service, &Input) -> Fut,
   |             --------------------------- required by this bound in `call_provider_method`
...
65 |     let result = call_provider_method(&server, Calc::div, input.as_bytes()).await;
   |                  ^^^^^^^^^^^^^^^^^^^^ expected signature of `for<'r, 's> fn(&'r Server, &'s _) -> _`

error[E0271]: type mismatch resolving `for<'r, 's> <fn(&_, &CalcRequest) -> std::pin::Pin<std::boxed::Box<dyn std::future::Future<Output = std::result::Result<CalcResponse, CalcError>> + std::marker::Send>> {<_ as Calc>::div::<'_, '_, '_>} as std::ops::FnOnce<(&'r Server, &'s _)>>::Output == _`
  --> src/main.rs:65:18
   |
44 | async fn call_provider_method<'a, Service, Method, Input, Output, Fut>(
   |          -------------------- required by a bound in this
...
50 |     Method: Fn(&Service, &Input) -> Fut,
   |                                     --- required by this bound in `call_provider_method`
...
65 |     let result = call_provider_method(&server, Calc::div, input.as_bytes()).await;
   |                  ^^^^^^^^^^^^^^^^^^^^ expected bound lifetime parameter, found concrete lifetime

When changing the Calc::div function to take a CalcRequest instead of a &CalcRequest and removing the & from &Service , &Input and &request the code compiles:

I do however want the functions in the trait not to consume the request but accept a reference.

Your generic arguments say that the future returned by f may not borrow from the references passed to f, but the future returned by Calc::div does in fact borrow from those arguments, hence the error.

There is no way to specify that the returned future may borrow from the arguments currently.

Thanks for clearing them up. So the problem is not the trait at all. It's the passing of a function that takes a reference. I might be solving this differently. Since a lot of this is generated code anyways I can just inline the generate_provider_method. It looks like code smell but if that helps the compiler figure out the required lifetimes that's probably the only way to go:

impl Server {
    async fn call<'a>(&self, method: &str, input: &'a [u8]) -> Result<Vec<u8>, CalcError> {
        match method {
            "div" => {
                let request: CalcRequest = serde_json::from_slice(input).map_err(|e| CalcError::DeserializerError(e))?;
                let output = self.div(&request).await?;
                let response = serde_json::to_vec(&output).map_err(|e| CalcError::SerializerError(e))?;
                Ok(response)
            }
            _ => Err(CalcError::NoSuchMethod),
        }
    }
}

Thanks a lot @alice for clearing this up for me. I do now get what the problem is.

The error message itself though is still a riddle to me.

Now that I think a bit more about that it's not only about f needing to borrow from the arguments of call_provider_method. When calling the trait method I also need to borrow the CalcRequest object which is created inside the call_provider_method function. Thus I would need to convert the call_provider_method function to return a boxed future so it can borrow the Input to the tait method.

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.