Home > Net >  Why can't Rust coerce an async function with a lifetime parameter to a function pointer?
Why can't Rust coerce an async function with a lifetime parameter to a function pointer?

Time:10-08

I'd like to write a function which takes one argument: a pointer to an async function taking a lifetime parameter.

Here's a minimal example:

use std::future::Future;

async fn takes_i32_ref<'a>(r: &'a i32) { }

fn takes_fn<F: Future>(f: for<'a> fn(&'a i32) -> F) { }

fn main() {
    takes_fn(takes_i32_ref);
}

When I try to run this I get the message:

error[E0308]: mismatched types
 --> src/main.rs:8:14
  |
8 |     takes_fn(takes_i32_ref);
  |     -------- ^^^^^^^^^^^^^ one type is more general than the other
  |     |
  |     arguments to this function are incorrect
  |
  = note: expected fn pointer `for<'a> fn(&'a i32) -> _`
                found fn item `for<'a> fn(&'a i32) -> impl for<'a> Future<Output = ()> {takes_i32_ref}`
note: function defined here
 --> src/main.rs:5:4
  |
5 | fn takes_fn<F: Future>(f: for<'a> fn(&'a i32) -> F) { }
  |    ^^^^^^^^            ---------------------------

I found a question that seemed pretty related: Why isn't `std::mem::drop` exactly the same as the closure |_|() in higher-ranked trait bounds?

I think that question is different from mine though. In that question, a for<'a> FnOnce<(&'a &str,)> is expected and a FnOnce<(&&str,)> is provided. The provided type is less general than the expected type because it lacks a for.

However, my example says that a for<'a> fn(&'a i32) -> _ is expected and a for<'a> fn(&'a i32) -> impl for<'a> Future<Output = ()> {takes_i32_ref} is provided. The provided type is more general than the expected type because it has a for.

Why can't Rust do this coercion?

Edit: I should also add that this error doesn't appear when I get rid of the reference in the signature of takes_i32_ref and f. That the async function takes a parameter with a lifetime seems to be important.

CodePudding user response:

The future returned by an async function is only valid for as long as the arguments are. Therefore, the return type may depend on the argument lifetimes. So, per the original RFC, the type of takes_i32_ref is really something like

fn takes_i32_ref<'a>(r: &'a i32) -> impl Future<Output = ()>   'a

Now it should be clear why your code doesn't work: takes_fn's F cannot depend on 'a; you've written that takes_fn takes a function that, for all 'a, returns the same type F. takes_i32_ref is not such a function, so takes_fn(takes_i32_ref) fails.

In an alternate universe/future Rust, this might be fixable with higher-kinded polymorphism:

// imaginary syntax
fn takes_fn<F<'a>: Future   'a>(f: for<'a> fn(&'a i32) -> F<'a>) { }
// which would be the same as (also imaginary, but at least this one parses...)
fn takes_fn(f: for<'a> fn(&'a i32) -> (impl Future   'a)) { }

But I do not think your code is possible to make work today. It is simply not that easy to deal with a function with as many complex type/lifetime dependencies as yours.

To force it to work, for a nonzero performance penalty, you can use dyn.

fn takes_fn<O>(f: for<'a> fn(&'a i32) -> Box<dyn Future<Output = O>   'a>) { }

fn main() {
    takes_fn(|r| Box::new(takes_i32_ref(r)));
}
  • Related