Lifetime bounds to use for Future that isn't supposed to outlive calling scope

Hello,

I am hitting everybody's favorite error, where the expected type looks identical to the provided type even though the interpretation of the lifetime bounds differ in a subtle way that isn't part of the error message.

Anyway, I believe the issue is that the future thinks it is bounded such that it needs to outlive the calling context. Along the lines of this: Error: one type is more general than the other - #6 by daboross

In my case, I intend to await the future before the calling context is exited. So how can I tweak the bounds to express this?

Thank you.

A general solution is BoxFuture in futures::future - Rust
Rust Playground

A way to avoid Box is to use a trait to express the lifetime connection between the input argument and return Fut: Rust Playground

3 Likes

10/10 can confirm, that’s one of my “favorite” kind of errors, too. :sweat_smile: Maybe we should build a dedicated detection for this case, apologizing for the unhelpful error message, and recommending to report an issue.

1 Like

I’ve written answers on similar problems in the past. E.g. here or here and the second one links some more places.

It’s possible to avoid the need for Box using trait, and @vague above provided an example, too, but those will unfortunately (for no good reason) not work with closures, only with (async) fn items. In that sense, the BoxFuture approach is more flexible/easy to use, at the minor downside of adding an allocation and some dynamic function calls.

(For example in both @vague's and @quinedot's playgrounds, you cannot write something like .dispatch_request(|x: &mut String| test_interpreter(x)) without incomprehensible and unavoidable compilation errors; so in particular, you can never pass a callback that needs to actually capture some data.)

3 Likes

You can make test_interpreter not capture a lifetime, by e.g. cloning everything before an async block:

fn test_interpreter(input_stream: &mut String) -> impl Future<Output = Result<String, Error>> {
    let input_stream = input_stream.clone();
    async move {
        Ok(input_stream)
    }
}

If you don't want to do that, the actual problem is that here:

        /* <T, F, Fut> */ where
        F: FnMut(&mut String) -> Fut,
        Fut: Future<Output = Result<T, Error>>,

The type parameter Fut has to resolve to a single type, but with test_interpreter, the output type is parameterized by the input lifetime.

@vague beat me to how one works around that without type erasure, but here's another implementation.

2 Likes

@vague @quinedot @steffahn Wow! All three of your answers are wonderful and each provides a unique perspective. I am having a very hard time deciding which to mark as the solution.

I filed implied lifetimes lead to mismatched type error with seemingly identical types in the msg - async closure edition · Issue #108114 · rust-lang/rust · GitHub

Hopefully that's the right place to do it.

Thanks again!

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.