Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

rust

lifetime

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?

like image 225
Yohei Avatar asked Jan 18 '21 06:01

Yohei


1 Answers

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:

  • The compiler suggests I add a 'static lifetime because the parameter type may not live long enough, but I don't think that's what I want
  • How can this instance seemingly outlive its own parameter lifetime?
  • How can we write a generic function for checking Serde serialization and deserialization?
like image 137
E_net4 stands with Ukraine Avatar answered Nov 04 '22 15:11

E_net4 stands with Ukraine