Expressing lifetime of async method's future

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.

What I'm trying to do is this: Rust Playground

I would appreciate any help on the subject.

A similar question.

1 Like

Easy solution Rust Playground

// 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()

Same problem as Lifetime bounds to use for Future that isn't supposed to outlive calling scope where some solutions are provided with explanations for limitation by others.

Maybe the problem and solutions should be memtioned somewhere in official materials, like async book or the book.

1 Like

Thank you so much, that works!
I would've expected

    fn wrap_request<'a, ReqFn, Res, Fut>(&self, do_request: ReqFn) -> Result<Res, Status>
        where ReqFn: Fn(&'a mut Client) -> Fut,
              Fut: Future<Output=Result<Response<Res>, Status>> + 'a,

to have the same effect, but apparently my understanding of Rust's lifetime syntax is wrong

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.

2 Likes

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.

3 Likes

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.

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.