25

I have a trait that I'm using to abstract away tokio::net::TcpStream and tokio::net::UnixStream:

/// Interface for TcpStream and UnixStream.
trait TryRead {
  // overlapping the name makes it hard to work with
  fn do_try_read(&self, buf: &mut [u8]) -> Result<usize, std::io::Error>;
}

impl TryRead for TcpStream {
  fn do_try_read(&self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
      self.try_read(buf)
  }
}

The problem is that I want to abstract away pub async fn readable(&self) -> io::Result<()> in both methods but async methods cannot be implemented in traits. How can I handle this?

4 Answers 4

54

On stable Rust, async fn cannot be used in traits. There is ongoing work that will make this possible in the future, but the easiest solution currently is to use the async-trait crate:

#[async_trait]
trait Readable {
    async fn readable(&self) -> io::Result<()>;
}

#[async_trait]
impl Readable for Reader {
    async fn readable(&self) -> io::Result<()> {
        do_stuff().await
    }
}

To avoid having Send bound placed on the async trait methods, you can invoke the async trait macro as #[async_trait(?Send)] on both the trait and the impl blocks.

#![feature(async_fn_in_trait)]

On nightly, it is now possible to write async trait methods using the async_fn_in_trait feature:

#![feature(async_fn_in_trait)]

trait Readable {
    async fn readable(&self) -> io::Result<()>;
}


impl Readable for Reader {
    async fn readable(&self) -> io::Result<()> {
        do_stuff().await
    }
}

However, the current implementation is limited, and does not allow specifying Send or Sync bounds on the returned future. See the announcement post for details.

Associated Types

Another way of doing it is with an associated type:

trait Readable {
    type Output: Future<Output = io::Result<()>>;

    fn readable(&self) -> Self::Output;
}

When implementing this trait, you can use any type that implements Future, such as Ready from the standard library:

use std::future;

impl Readable for Reader {
    type Output = future::Ready<io::Result<()>>;
    
    fn readable(&self) -> Self::Output {
        future::ready(Ok(()))
    }
}

async functions return an opaque impl Future, so if you need to call one inside your function, you can't have a concrete Output type. Instead, you can return an dynamically typed Future:

impl Readable for Reader {
    // or use the handy type alias from the futures crate:
    // futures::BoxFuture<'static, io::Result<()>>
    type Output = Pin<Box<dyn Future<Output = io::Result<()>>>>;
    
    fn readable(&self) -> Self::Output {
        Box::pin(async {
            do_stuff().await
        })
    }
}

Note that using these trait methods will result in a heap allocation and dynamic dispatch per function-call, just like with the async-trait crate. This is not a significant cost for the vast majority of applications, but is something to be considered.

Capturing References

One issue that may come up is the fact that the associated type Output does not have a lifetime, and therefore cannot capture any references:

struct Reader(String);

impl Readable for Reader {
    type Output = Pin<Box<dyn Future<Output = io::Result<()>>>>;
    
    fn readable(&self) -> Self::Output {
        Box::pin(async move {
            println!("{}", self.0);
            Ok(())
        })
    }
}
error: lifetime may not live long enough
  --> src/lib.rs:17:9
   |
16 |       fn readable(&self) -> Self::Output {
   |                   - let's call the lifetime of this reference `'1`
17 | /         Box::pin(async move {
18 | |             println!("{}", self.0);
19 | |             Ok(())
20 | |         })
   | |__________^ returning this value requires that `'1` must outlive `'static`

Associated types on stable Rust cannot have lifetimes, so you would have to restrict the output to a boxed future that captures from self to make this possible:

trait Readable {
    // note the anonymous lifetime ('_) that refers to &self
    fn readable(&self) -> Pin<Box<dyn Future<Output = io::Result<()>> + '_>>;
}

impl Readable for Reader {
    fn readable(&self) -> Pin<Box<dyn Future<Output = io::Result<()>> + '_>> {
        Box::pin(async move {
            println!("{}", self.0);
            Ok(())
        })
    }
}

This is actually exactly what the async-trait crate does under the hood.

Unstable Features

If you are on nightly, the story is better. You can enable the type_alias_impl_trait feature and use regular async/await syntax without boxing:

#![feature(type_alias_impl_trait)]

trait Readable {
    type Output: Future<Output = io::Result<()>>;

    fn readable(&self) -> Self::Output;
}


impl Readable for Reader {
    type Output = impl Future<Output = io::Result<()>>;
    
    fn readable(&self) -> Self::Output {
        async { ... }
    }
}

The borrowing issue still applies with the above code. However, with the recently stabilized generic associated type feature, you make Output generic over a lifetime and capture self:

trait Readable {
    type Output<'a>: Future<Output = io::Result<()>>;

    fn readable(&self) -> Self::Output<'_>;
}

And the previous example compiles, with zero boxing!

struct Reader(String);

impl Readable for Reader {
    type Output<'a> = impl Future<Output = io::Result<()>> + 'a;
    
    fn readable(&self) -> Self::Output<'_> {
        Box::pin(async move {
            println!("{}", self.0); // we can capture self!
            Ok(())
        })
    }
}
7
  • 3
    It's also worth noting that async-trait can avoid the Send bound by using #[async_trait(?Send)] on the trait and implementations Commented Jan 27, 2021 at 18:58
  • tokio::net::TcpStream::readable() returns io::Result<()> and when I try your first snippets I fail with the error "the Output of this async fn's found opaque type".
    – ruipacheco
    Commented Jan 28, 2021 at 16:37
  • @ruipacheco I mentioned this in my answer: "In cases where you can't statically type your result, you can return an owned dynamically typed Future" Commented Jan 28, 2021 at 16:50
  • 1
    I've refactored my code to use async_trait but still have the same problem.
    – ruipacheco
    Commented Jan 28, 2021 at 21:17
  • 1
    rust-analyzer was giving me the wrong output, problem solved. Thanks!
    – ruipacheco
    Commented Jan 29, 2021 at 8:31
4

At the moment, it is not possible to use async methods in traits. While that feature gets stabilized (and it will probably take quite some time), the only solution I know is the async_trait crate.

use async_trait::async_trait;

#[async_trait]
trait Readable {
    fn async readable(&self) -> io::Result<()>;
}

The async_trait macro basically just turn your function into the following code:

trait Readable {
    fn readable<'a>(&self) -> Pin<Box<dyn 'a + Send + Future<Output = io::Result<()>>>
}

The downside of this method is the additional cost of a trait object.

3
  • 2
    async-trait turns your function into a Pin<Box<dyn Future<...>>> with a Send bound Commented Jan 27, 2021 at 15:51
  • Can someone update the example for future reference?
    – ruipacheco
    Commented Jan 27, 2021 at 17:10
  • Yep, done, thanks for pointing that out. I was pretty sure I remembered how the code was expanded
    – Gymore
    Commented Jan 27, 2021 at 21:29
0

According to a recent post: https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html. I think we could take a shot in 1.67.0-nightly (2022-11-17).

edit:

I make a sample here: https://gist.github.com/rust-play/489285f2cfefdcc4849d2ff1ad370ce3

3
  • So this is "I read about something which might help, but I never tried it." ? Could you try for How to Answer?
    – Yunnosch
    Commented Nov 25, 2022 at 7:08
  • Sorry about that. I noticed the article had a playground sample there, so I didn't create my own. I just provide my own sample here, thanks ur remind.
    – user9032741
    Commented Nov 25, 2022 at 12:27
  • What you added is only a link, which is not considered a contribution to an answer on StackOverflow.
    – Yunnosch
    Commented Nov 27, 2022 at 21:22
0

Just a quick update: The async_fn_in_trait pull request has been merged, and should be in stable rust by version 1.75, due for release on the 28th December 2023. See the pull request for full details of what this PR enables. In short, you'll be able to do this (code example taken from the PR):

trait Bar {
    async fn bar(self);
}

impl Bar for () {
    async fn bar(self) {}
}
0

Not the answer you're looking for? Browse other questions tagged or ask your own question.