Accept different type as argument

Hi everyone,

I got a function and take only one argument.
Is it possible to make it accept both fn and async fn as argument?

I tried to make it accept both via creating a new trait and impl this trait for both fn and async fn,
but it seems doesn't work.

here is a simplified way of how i tried it.

Yes and no. async functions are really just functions that happen to return a type that implements Future, so you can write something like this:

fn my_func<F,R>(f: &F)->R where f:Fn()->R {
    // do stuff...
    f()
}

This is often pretty limiting, though, because you can't really do anything with the return type other than return it from your own function. Without more information about what you're trying to do, it's hard to say whether or not it's possible.

1 Like

in this way R could be one impls Future, but normal fn doesn't return sth impls Future, how can non-async func accept as argument here.

Two function pointers, one returning T and the other, async version returning some type F where F implements Future<Output=T> are distinct types. So you can't pass either one as your argument without a wrapper. That could be an enum for example, or you could convert the synchronous version into an asynchronous version by wrapping it in a closure with an async body:

use futures::future::Future;
use futures::executor::block_on;

fn foo() -> u8 {
    0
}

async fn foo_async() -> u8 {
    0
}

fn bar<F, O>(f: F) where F: Fn() -> O, O: Future<Output=u8> {
    println!("{}", block_on(f()));
}

fn main() {
    bar(|| async { foo() });
    bar(foo_async);
}

Playground.

Or the other way around:

use futures::executor::block_on;

fn foo() -> u8 {
    0
}

async fn foo_async() -> u8 {
    0
}

fn bar<F>(f: F) where F: Fn() -> u8 {
    println!("{}", f());
}


fn main() {
    bar(foo);
    bar(|| block_on(foo_async()));
}

Playground.

1 Like

Reminds me of keyword generics

1 Like

i c..

so right now it is not possible to just create one fn accept both without wrapper..
thank you~

1 Like

Just a note that every example in this thread has used a type parameter for the return value, which won't work for a Fn(...) that returns more than one type (e.g. where the return type captures a lifetime from the inputs).

(I think the OP has their answer, so I'm not going to go through a full reproduction of a helper trait for that.)

Even if it's technically possible, I advise not to do it. It will create a needlessly complex API.

Pick one, and offer just that.

If you only support async fn, then users who have sync code to call will be easily able to adapt by wrapping it in async { regular_fn() } block.

Even if you only support sync fn, then move || handle.block_on(async {…}) might be possible for callers.

So you should decide whether you call the callbacks in context that needs to support async, or whether the callback can block you code. Don't add vague complex interfaces "just in case".

1 Like

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.