Is it possible to annotate the lifetime of a future/async block?

I think I have a question about the "lifetime" of a Future returned by a function... although I'm not sure this is the right word for it, because "lifetime" usually refers to a reference. But what I mean is: I want to return a Future which holds a reference to some data, and I want the caller to guarantee/compiler to enforce that the return promise will be used or thrown away by that particular named lifetime.

Is there a way to explicitly annotate this kind of promise?

In addition, is there a way to annotate an async block so that the Future it produces has this explicit named lifetime instead of 'static?

Example code. This function works because the compiler knows that the future is immediately awaited:

async fn test_works() {
    let s = "ref";
    let promise = async {
        println!("Running async block: {}", s);
    };
    promise.await
}

I'd like to replicate this by having multiple functions and replacing the automatic lifetime inference with something explicit. e.g.:

fn test_broken_inner(s: &str) -> impl std::future::Future {
    async {
        println!("Running async block: {}", s);
    }
}

async fn test_broken_outer() {
    let s = "broken";
    test_broken_inner(s).await;
}

Is there any way to explicitly annotate the future like that? I tried some explicit lifetimes but they don't work. Is this a place where PhantomData would somehow help identify the lifetime of the Future? (can traits express phantom lifetime relationships, or only concrete types?)

fn test_broken_inner<'a>(s: &'a str) -> impl std::future::Future<'a> {
fn test_broken_inner<'a>(
    s: &'a str,
) -> impl std::future::Future + 'a {

Note that this lifetime pattern is covered by the lifetime elision rule.

Your comment is true for standard reference lifetimes, but I'm not sure how it's relevant to my question here. The code example here doesn't compile because Future doesn't have a way to annotate its lifetime (at least that I've found).

If you use the function signature @Hyeonu posted, then all that's missing is an additional change to async move, and you're set:

fn test_broken_inner<'a>(s: &'a str) -> impl std::future::Future + 'a {
    async move {
        println!("Running async block: {}", s);
    }
}

async fn test_broken_outer() {
    let s = "broken";
    test_broken_inner(s).await;
}

(the names still say "broken" but the code above actually compiles)

Of course, one would usually use an equivalent async fn for this:

async fn test_broken_inner<'a>(s: &'a str) {
    println!("Running async block: {}", s);
}

Lifetime elision allows you to, equivalenty, write:

fn test_broken_inner(s: &str) -> impl std::future::Future + '_ {

or

async fn test_broken_inner(s: &str) {

respectively.

2 Likes

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.