6
votes

I'd like to write async functions in a trait, but since async fn in traits are not supported yet, I am trying to find the equivalent method interface. This is what I have tried in Rust nightly (2019-01-01):

playground

#![feature(await_macro, async_await, futures_api)]
#[macro_use]
extern crate tokio;
use tokio::prelude::*;

trait T {
    async fn f();
}

fn main() {
}
error[E0706]: trait fns cannot be declared `async`
 --> src/main.rs:7:5
  |
7 |     async fn f();
  |     ^^^^^^^^^^^^^

I read somewhere that async is just impl Future.

trait T {
    fn f() -> impl futures::Future<Item = (), Error = ()>;
}
error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
 --> src/lib.rs:2:15
  |
2 |     fn f() -> impl futures::Future<Item = (), Error = ()>;
  |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Directly returning impl trait is not allowed, so I tried a boxed trait:

trait Resource {
    fn loaded(&self) -> bool;
    fn init(&mut self, auth: &str) -> Box<dyn std::future::Future<Output=()>>;
    fn prepare(&mut self, auth: &str) -> Box<dyn std::future::Future<Output=()>> {
        Box::new(async {
            if !self.loaded() {
                await!(*(self.init(auth)));
            }
        })
    }
}
[rustc] the size for values of type `dyn std::future::Future<Output=()>` cannot be known at compilation time

Without deref, I get the error that no into_awaitable exists for Box<>.

Can I use non-Sized impl Future or *Box<Future> with await!? What is the most suitable interface for async functions in the trait?

2
"Directly returning impl trait is not allowed" — what makes you say that?Peter Hall
Ah. You mean it's not allowed in a trait. Yes, in this situation you will need to return a trait object.Peter Hall
Please review how to create a minimal reproducible example and then edit your question to include it. We cannot tell what crates, types, traits, fields, etc. are present in the code, or even what version of Rust you are using! Try to produce something that reproduces your error on the Rust Playground or you can reproduce it in a brand new Cargo project. There are Rust-specific MCVE tips as well.Shepmaster
Should I include a Rust Playground link for each short example?mq7

2 Answers

2
votes

Neither async functions nor impl Trait are allowed in traits. You can use associated types to get closer. Here are some ideas:

pub trait ResourceTrait {
    type FutType: Future<Output = ()>;

    fn prepare(&mut self, auth: &str) -> Self::Next;
}

Implementing this is currently is a bit tricky, since some of the required tooling is either not yet available, stable or buggy.

It can be implemented as:

impl ResourceTrait for Resource {
    type FutType = FutureObj<'static, ()>;

    fn prepare(&mut self, auth: &str) -> FutureObj<'static, ()> {
        FutureObj::new(Box::new(
            async move {
                // Do async things
                // You might get a lifetime issue here if trying to access auth,
                // since it's borrowed.
            }
        ))
    }
}

An alternative with existential types might be:

impl ResourceTrait for Resource {
    // this is required since the real type of the async function
    // is unnameable
    existential type FutType = Future<Output = ()>;

    fn prepare(&mut self, auth: &str) -> Self::FutType {
        async move {
            // Do async things. Might still encounter the same borrowing issues,
            // since the lifetime of the returned Future isn't coupled to the
            // lifetime of self.
            // The workaround is to make copies of all required fields and move
            // them into the Future
        }
    }
}

This might or might not work (since the feature is work in progress). For properly borrowing parameters like self or auth in the returned future, we might also need Generic Associated Types to be available first.

In order to workaround the borrowing problems for self, you might be able to define

struct Resource {
    inner: Arc<ResourceInner>, // carries all actual state
}

so that you can copy inner in prepare and move it into the Future.

1
votes

If you're okay with returning a boxed future, you can use the async-trait crate:

#![feature(async_await)]

use async_trait::async_trait;

#[async_trait]
trait Advertisement {
    async fn run(&self);
}

struct Modal;

#[async_trait]
impl Advertisement for Modal {
    async fn run(&self) {
        self.render_fullscreen().await;
        for _ in 0..4u16 {
            remind_user_to_join_mailing_list().await;
        }
        self.hide_for_now().await;
    }
}

struct AutoplayingVideo {
    media_url: String,
}

#[async_trait]
impl Advertisement for AutoplayingVideo {
    async fn run(&self) {
        let stream = connect(&self.media_url).await;
        stream.play().await;

        // Video probably persuaded user to join our mailing list!
        Modal.run().await;
    }
}