Home > Back-end >  How to define a function that receive a async closure as parameter without a where clause
How to define a function that receive a async closure as parameter without a where clause

Time:01-03

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?

CodePudding user response:

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.

CodePudding user response:

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