How to define a function that receive a async closure as parameter without a where clause

Viewed 268

When trying to write a function that receives a async closure, I found it is a little bit tricky.

I know correct version is

pub async fn f1<F, Fut>(f: F) -> u32
where
    F: Fn(u32) -> Fut,
    Fut: Future<Output = u32>,
{
    f(9u32).await
}

However, the where clause in the Rust doc saying that it is just a convenient way to express those type, so I tried to remove the where clause like this:

pub async fn f2(f: Fn(u32) -> Future<Output = u32>) -> u32 {
    f(9u32).await
}

To my surprise, Rust refused to compile it, saying that

error: trait objects must include the dyn keyword

Anyone know what is the correct way to remove the where clause?

Any magic is played here by the where clause?

2 Answers

In your first snippet you have to constraints:

  • F must implement Fn(u32) -> Fut.
  • Fut must implement Future<Output = u32>.

But in your second snippet there are no constraints. The syntax for an inline constraint is with the impl keyword:

pub async fn f2(f: impl Fn(u32) -> Future<Output = u32>) -> u32 {
    f(9u32).await
}

This doesn't work either because the impl applies to the Fn trait only, not to the return trait, so the compiler complains that a naked trait is not allowed, that you should add dyn to it.

Bad advice, because:

pub async fn f2(f: impl Fn(u32) -> dyn Future<Output = u32>) -> u32 {
    f(9u32).await
}

will not work either, because dyn Trait types are unsized and cannot be returned.

You could try another impl there, that usually works in return types but:

pub async fn f2(f: impl Fn(u32) -> impl Future<Output = u32>) -> u32 {
    f(9u32).await
}

impl Trait` not allowed outside of function and method return types

The only syntax I know of to constraint the return type of an Fn trait is with the where syntax:

pub async fn f2<Fut>(f: impl Fn(u32) -> Fut) -> u32
where
    Fut: Future<Output = u32>,
{
    f(9u32).await
}

But now that you are in full where mode, I see little reason to keep the inline constraint. Personally I consider bad style having both syntaxes on the same function.

Assuming you're referencing Rust by Example:

The example there doesn't use trait objects (e.g. foo: Box<dyn SomeTrait>) or existential types (e.g. foo: impl SomeTrait) but adds bounds to generic types at the declaration site:

impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {}

// Expressing bounds with a `where` clause
impl <A, D> MyTrait<A, D> for YourType where
    A: TraitB + TraitC,
    D: TraitE + TraitF {}

In your case, you'd be writing:

pub async fn f2<F: Fn(u32) -> Fut, Fut: std::future::Future<Output = u32>>(f: F) -> u32 {
    f(9u32).await
}

instead of putting the Fn(u32) -> Future<Output = u32> bound as the type of f. Note that you also need to declare a generic type Fut: Future<Output = u32> for the returned future from the closure, otherwise you'd be returning a raw trait object again.

As mentioned by @rodrigo, it's also possible to use the impl SomeTrait notation to entirely remove the generic type parameter from the function.

Related