0

Playing around with async/await I stumbled across this lifetime puzzle. I assume that predicate takes a reference/value and produces static future - it has no reference to its arguments and captures no values. Instead I got complain about lifetime being too short.

Why it fails and where's the lifetime '2?

use futures::future;
use std::future::Future;

fn validate<F, Fut>(_: F)
where
    F: FnMut(&str) -> Fut,
    Fut: Future<Output = bool>,
{
}

fn predicate(_: impl AsRef<str>) -> impl Future<Output = bool> {
    future::ready(true)
}

fn main() {
    validate(|x| predicate(x));
}

Error message:

error: lifetime may not live long enough
  --> src\main.rs:16:18
   |
16 |     validate(|x| predicate(x));
   |               -- ^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
   |               ||
   |               |return type of closure is impl futures::Future
   |               has type `&'1 str`

error: aborting due to previous error

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
flopacero
  • 187
  • 2
  • 8
  • 1
    I think it is related to this other [question](https://stackoverflow.com/a/59101369/865874). You can fix it by adding another generic to your `validate`: `where R: AsRef, F: FnMut(R) -> Fut,`... – rodrigo Sep 28 '20 at 10:39
  • The abyss gaped upon me. I definitely need some time to process it. Thanks for pointing out to the question. – flopacero Sep 28 '20 at 18:12

1 Answers1

2

The error message here is a little confusing because it's talking about two lifetimes, '1 and '2, but doesn't actually say what '2 refers to.

There are two lifetimes that matter here:

  • the &str argument, which the message labels with lifetime '1
  • the future type variable Fut, which is labeled with lifetime '2 in the message, though it doesn't clearly state that.

The inferred lifetimes (using the lifetime elision rules) for predicate are like this:

fn predicate<'x>(_: impl AsRef<str> + 'x) -> impl Future<Output = bool> + 'x {
    future::ready(true)
}

Even though the implementation doesn't actually use the string input, it would be weird if it didn't, so this is probably correct for a real implementation. The compiler has inferred that the return value of a function uses the input, so they must have compatible lifetimes.

However, the lifetimes for validate are inferred to be different:

fn validate<'1, '2, F, Fut>(_: F)
where
    F: FnMut(&'1 str) -> Fut,
    Fut: Future<Output = bool> + '2,
{
}

This function accepts arguments with broader requirements than predicate, which means it can't guarantee to enforce the constraints that predicate needs.

You can fix that by making it explicit in the type of validate:

fn validate<'a, F, Fut>(_: F)
where
    F: FnMut(&'a str) -> Fut,
    Fut: Future<Output = bool> + 'a,
{
}

Constraining both types with the 'a lifetime communicates that validate expects F to use data from the &str, and therefore will ensure that the string lives long enough. Any implementation of validate that broke this contract would also not compile.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • I wounder why it still fails if I change `predicate` return type to `impl Future + 'static`. I thought `'static` should fit any lifetime restriction then. – flopacero Sep 28 '20 at 17:42
  • At the same time if I replace return type with direct `future::Ready` it works perfectly well. I expected it to be equivalent to `impl Future + 'static` – flopacero Sep 29 '20 at 06:06