I'm pretty new to Rust, and I'm making a sync wrapper around async client (yeah thank you so very much for that, prost) that blocks until the future is done, returning the result. To avoid writing the same boilerplate every time I want to "call" a client method, I'm trying to do a generic method wrap_request that would take the closure returning the Future, block on it and return the inner result.
However, I can't think of a way to express lifetimes here. Returned future, being an async method, has a reference to a client, so client reference needs to live for at least as long as the future does. At the same time, client is behind a RefCell so its mutable reference has a local lifetime.
// step1: explicit lifetime relation between `BoxFuture` and `&mut Client` in the trait bound
ReqFn: Fn(&mut Client) -> BoxFuture<'_, Result<Response<Res>, Status>>
// step2: construct a BoxFuture via Box::pin() or .boxed()
That's a generic lifetime parameter. Generic parameters are chosen by the caller, not by the function body. In fact, any lifetime parameter declared in the function signature is by definition strictly longer than anything you can conjure from within the function body, so what you wrote can never work.
After all, what would happen if your function were called with e.g. the 'static lifetime? It just doesn't make any sense – your local variable would have to live forever, which is quite obviously not the case.
This is probably the whole raison d'être for higher-order lifetimes and traits.
The caller is also the one that defines the closure in question, shouldn't it in principle be able to pick 'a to match the working example (where ReqFn: for <'a> Fn(&'a mut Client) -> BoxFuture<'a, ...>) ?
No, because that function signature means "I return a BoxFuture with the same lifetime as that of the reference in my argument". Obviously, that can't be true when the caller chooses the lifetime parameter but the function body chooses the actual scope of the Client (and therefore the longest possible lifetime of any &mut Client).
Again, if the caller calls wrap_request::<'static, _, _>(…), then the returned future must have a 'static lifetime in it, which is impossible, since it refers to a non-'static temporary.
It doesn't matter who defines the closure, it's a red herring, it doesn't affect anything. It must satisfy the declared signature, no matter where its own value comes from. If its signature says it returns a type with the same lifetime with its argument, then it must do exactly that, period.
It seems as if the helper trait solution mentioned in the first answer unfortunately still doesn't work for closures so the solution with BoxFuture might probably be the best way to go here indeed.