Lifetime issue closure returning future continued

When trying to work with closures that provide futures I am running into a number of types I have trouble expressing properly. How should the type T in the code below be properly specified in the generic code to allow the call from main below?

async fn foo<C, T>(f: C) -> bool
where
    T: std::future::Future<Output=()>,
    C: for <'a> FnOnce(&'a str) -> T
{
    let bar = "str".to_string();
    f(&bar).await;
    true
}

#[tokio::main]
async fn main() {
    let mut baz = "bla".to_string();
    foo(|bar| async { baz.push_str(bar); }).await;
}

Playground link: Rust Playground

Essentially I would like to express the constraint that T should not live longer than 'a, but I haven't yet found a way to write that down legally. The "obvious" way of writing (T+'a) as the closure return type is not legal in rust 1.78. I have found the previous thread suggesting BoxFuture, but I would strongly prefer to avoid those as I want this to work in an environment without alloc.

See here and the following comment for an explanation. (Your case is a little simpler because there's no method receiver.) Or rephrased: T can never represent the future returned from your closure, because that future captures a lifetime -- it's parameterized by the input lifetime; it's not a single type.

There are a couple other hurdles to your OP:

  • Your closure also captuers a &mut to baz. Was that intentional?
    • If so, complicates things -- now there's another lifetime at play
  • Even if you don't need that, Rust's inference of closures you want to be higher-ranked is pretty bad

Here's a playground for the case where you don't need to capture a &mut baz. It demonstrates the second hurdle too.

If there's a way around the first bullet point without boxing, I think it'd run into the second bullet point too -- and not have a workaround using async fn.

The capture of &mut baz was very intentional, it is quite critical to the use cases I want to support. I could deal with having to be more explicit on types if that is the solution for the second point. However, for the first I would really love to have some workaround, this feels like a quite strong missing factor in the type system. Is this a known issue of the type system?

You can express the signature for foo using a trait such as async_fn_traits supplies, to avoid mentioning the Future type at all.

async fn foo<C>(f: C) -> bool
where
    C: for<'a> async_fn_traits::AsyncFnOnce1<&'a str, Output = ()>, 
{

The closure/async-block captures don't always cooperate with this well, so it won't necessarily work but it's worth trying.

The inference problems are well known; the below issue or one of many related ones.

I think the type system could support it with enough hand-holding, but figuring out the hand-holding is a challenge.

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.