3

I have async function to which I am passing async callback. The callback takes a reference as a parameter.

use core::future::Future;

async fn foo(bar: &u32) {}

async fn baz<F, Fut>(f: F)
where
    F: FnOnce(&u32) -> Fut,
    Fut: Future<Output = ()>,
{
    let test: u32 = 42;
    f(&test).await;
}

#[tokio::main]
async fn main() {
    baz(foo).await;
}

I am getting the following error if I try to build this (playground):

error[E0308]: mismatched types
  --> src/main.rs:16:5
   |
16 |     baz(foo).await;
   |     ^^^ lifetime mismatch
   |
   = note: expected associated type `<for<'_> fn(&u32) -> impl Future<Output = ()> {foo} as FnOnce<(&u32,)>>::Output`
              found associated type `<for<'_> fn(&u32) -> impl Future<Output = ()> {foo} as FnOnce<(&u32,)>>::Output`
   = note: the required lifetime does not necessarily outlive the empty lifetime
note: the lifetime requirement is introduced here
  --> src/main.rs:7:24
   |
7  |     F: FnOnce(&u32) -> Fut,
   |                        ^^^

I understand that it's not happy about the lifetime of the reference. However, I don't understand why.

  • We borrow "test"
  • We execute callback f (which is "foo")
  • There is no way for baz exits before f is done

So, it looks like there is no way for the borrow to outlive the place where test is declared.

What am I missing?

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
Victor Ronin
  • 22,758
  • 18
  • 92
  • 184

2 Answers2

4

The future returned by foo() has a hidden lifetime. The desugared signature is like:

fn foo<'a>(bar: &'a u32) -> impl Future<Output = ()> + 'a {
    async move {}
}

This is done so the function can hold bar across .await points. Sadly, what it means is that the function does not fulfill baz()'s bounds. The errors are obfuscated because the lifetime is hidden, but this is what the compiler is trying to tell you: the bound should be something like where F: for<'a> FnOnce(&'a u32) -> impl Future<Output = ()> + 'a, but you cannot express that in current Rust.

For more and potential solutions, see for example:

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • I think you can express it through traits, but it's a bit ugly: https://users.rust-lang.org/t/hrtb-on-multiple-generics/34255 – cdhowie May 13 '22 at 00:46
  • @cdhowie Yes, this is also mentioned (at least) on the first answer I linked. However, I meant cannot express in this way, i.e. there is no syntactic construct that allows you to bind HRTB to two generic parameters. – Chayim Friedman May 13 '22 at 01:00
1

Just for solving, you can use Box instead of references.

user-id-14900042
  • 686
  • 4
  • 17