2
pub trait GenericAsyncRunner{
    fn return_run_function(&self) -> ?;
}
pub enum AsyncRunError{}


pub struct SpecificAsyncRunner{}

impl SpecificAsyncRunner{
    async fn run(&self) -> Result<u32, AsyncRunError> {
        todo!()
    }
}

impl GenericAsyncRunner for SpecificAsyncRunner {
    fn return_run_function(&self) -> ? {
        todo!()
    }
}

I want to get a pointer to the async run function, at runtime. In practice I'll have let generic_async_runner = Box<dyn GenericAsyncRunner> and I want generic_async_runner.return_run_function().await. I could downcast but in practice there will be lots of different SomethingAsyncRunner so downcasting would have to try to downcast to all of them.

Can I rewrite async fn run() -> Result<u32, AsyncRunError> into a desugared version that returns a Future? In this way I can have a concrete type to put in ? on the code above.

Guerlando OCs
  • 1,886
  • 9
  • 61
  • 150
  • The type of the returned Future depends on the content of the function, i.e. it will differ by `impl`. Your best bet is to return a `Box>`, which is neatly taken care of by the `async_trait` crate. [See also](https://stackoverflow.com/a/57348266/401059). – Caesar Jul 01 '22 at 23:28
  • @Caesar can I do it without a trait – Guerlando OCs Jul 02 '22 at 00:09
  • By "it" you mean writing down the concrete type? No. You can desugar `async fn foo(…) -> T { …}` into `fn foo(…) -> impl Future { async { … } }`, but you can't go further. (Compared to `Box>`, it will save you the runtime overhead of an allocation, though.) – Caesar Jul 02 '22 at 00:48
  • 1
    You can't do this as written without boxing the future, because each implementation will have a different `Future` implementation. This is something that could potentially be done with an associated type, if Rust supported something like C++'s `decltype` as a mechanism to name the type of the future. Unfortunately, that can't be done today. – cdhowie Jul 02 '22 at 01:06

1 Answers1

2

async functions can't be specified in traits yet, and your example is one of the big reasons why. Your best bet is to return Box<dyn Future<Output=T>>, or use the async-trait crate that does that for you.

The issue is that for a method to be callable on a trait object, the method needs to return the exact same type for each of its implementations, so that the caller knows how to use the returned object, such as how many bytes to allocate for it.

Futures returned from async functions though are unique, and very opaque. They are very intimately tied to how the function gets compiled. A future returned by async fn foo() may be extremely different than the future returned by async fn bar() depending on their function bodies; for example, one may need to hold much more state than the other.

Boxing the futures is the primary way of solving this. By doing so, the information needed to access and execute the future is available and looked up at runtime, so different trait implementations can return different futures without breaking things. It's not great because of the allocation overhead, but it's a passable solution for a complex problem.

Colonel Thirty Two
  • 23,953
  • 8
  • 45
  • 85