How to declare generic parameters of the same type except for lifetime in rust?
Asked Answered
H

1

6

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?

Hollishollister answered 18/1, 2021 at 6:30 Comment(7)
You mean: fn f<'a, 'b: 'a, T>(v: &'a T, task: impl Future<Output = &'b T>) -> &'a T?Trawler
No. T is a struct containing lifetime parameter like Cow<'a, str>. So I cannot rewrite T as &'a T.Hollishollister
Your example code can trivially be fixed by using a second type parameter U for the output type of the Future. 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.Mlawsky
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.Hollishollister
I wonder why the compiler complains a's lifetime must be static, although T: 'a is required and sufficient.Hollishollister
The compiler wants it to be 'static because get returns a &'static str, so the compiler infers that T == &'static str.Trawler
@Trawler why doesn't the compiler infer T==&'a str?Hollishollister
D
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:

Dustup answered 18/1, 2021 at 13:4 Comment(1)
Thanks for your excellent answer! FYI, I simplified your example: play.rust-lang.org/…Hollishollister

© 2022 - 2024 — McMap. All rights reserved.