5

I wrote the code below, but I can't write life time constraint to work and get an error:

use futures::Future;

async fn foo<'a>(a: &'a str) -> &'a str {
    let task = get();
    f(a, task).await
}

async fn f<T>(v: T, task: impl Future<Output = T>) -> T {
    if true {
        v
    } else {
        task.await
    }
}

async fn get() -> &'static str {
    "foo"
}

error:

error[E0759]: `a` has lifetime `'a` but it needs to satisfy a `'static` lifetime requirement
 --> src/lib.rs:3:18
  |
3 | async fn foo<'a>(a: &'a str) -> &'a str {
  |                  ^  ------- this data with lifetime `'a`...
  |                  |
  |                  ...is captured here...
4 |     let task = get();
5 |     f(a, task).await
  |     - ...and is required to live as long as `'static` here

playground

I think it can be solved if two parameters in function f can have their own lifetimes. For example,

v: T,
task: S,
T: 'a,
S: 'b,
'b: 'a,
S == T

How to solve this issue?

tamuhey
  • 2,904
  • 3
  • 21
  • 50
  • You mean: `fn f<'a, 'b: 'a, T>(v: &'a T, task: impl Future) -> &'a T`? – Jmb Jan 18 '21 at 07:39
  • No. `T` is a struct containing lifetime parameter like `Cow<'a, str>`. So I cannot rewrite `T` as `&'a T`. – tamuhey Jan 18 '21 at 09:21
  • 1
    Your example code can [trivially be fixed by using a second type parameter `U` for the output type of the `Future`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1702ede49d4371854cf560346afc91a0). Your example code doesn't demonstrate why `T` and `U` need to be the same types except for their lifetimes. Your question only states an _attempted solution_ for a problem, but not the actual problem itself. Chances are that the solution isn't declaring two generic type parameters that are the same except for the lifetime, but we can't tell without more information. – Sven Marnach Jan 18 '21 at 10:41
  • Thanks. I fixed my example. The real problem I'm facing is more complicated, and I don't want to paste the code here because I want to show MVCE. – tamuhey Jan 18 '21 at 11:36
  • I wonder why the compiler complains `a`'s lifetime must be `static`, although `T: 'a` is required and sufficient. – tamuhey Jan 18 '21 at 11:39
  • The compiler wants it to be `'static` because `get` returns a `&'static str`, so the compiler infers that `T == &'static str`. – Jmb Jan 18 '21 at 16:07
  • @Jmb why doesn't the compiler infer `T==&'a str`? – tamuhey Jan 18 '21 at 16:14

1 Answers1

7

The same problem can be reproduced with another minimal example, using function interfaces instead of async functions.

fn get() -> impl FnOnce() -> &'static str {
    || "foo"
}

fn foo<'a, T: 'a, F>(_: &'a str, _: F)
where
    F: Fn() -> T,
    T: FnOnce() -> &'a str,
{
}

let x = "".to_string();
foo(&*x, &get);
error[E0597]: `x` does not live long enough
  --> src/main.rs:22:11
   |
22 |     foo(&*x, &get);
   |     ------^-------
   |     |     |
   |     |     borrowed value does not live long enough
   |     argument requires that `x` is borrowed for `'static`
23 | }
   | - `x` dropped here while still borrowed

This example allows us to turn get into a function parameter and observe that passing this function imposes a hard constraint for the lifetime 'a to be 'static. Despite the program's best intentions, a function returning a supplier function (or a promise) does not provide co-variance with respect to the output's lifetime. That is, () -> &'static str does not fulfill for<'a> () -> &'a str. Occasionally, the compiler will fallback to suggesting you to stick to the weakest link that is the 'static lifetime, even though this may not be desirable.

Note that the means of representing types which are generic over their lifetimes is quite limited at the moment. These are a form of higher kinded types, which can be specified only to some level of expressiveness via higher ranked trait bounds (and eventually generic associated types, once they are fully implemented and stabilized). In this case, rather than trying to make f work for a kind T<'a> (pseudo-code), it is much better to just make our get generic over the lifetime 'a. Subtyping may then take place at the implementation, as we know that a string literal can fulfill any lifetime.

fn get<'a>() -> impl FnOnce() -> &'a str {
    || "foo"
}

In the async case (Playground):

async fn get<'a>() -> &'a str {
    "foo"
}

See also:

E_net4
  • 27,810
  • 13
  • 101
  • 139
  • Thanks for your excellent answer! FYI, I simplified your example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d571d3cc6c68a7070a9c855f12c5aec3 – tamuhey Jan 18 '21 at 16:25