Jump to content
Main menu
Main menu
move to sidebar
hide
Navigation
Main page
Recent changes
Random page
Help about MediaWiki
Special pages
JOHNWICK
Search
Search
Appearance
Create account
Log in
Personal tools
Create account
Log in
Pages for logged out editors
learn more
Contributions
Talk
Editing
Rust: Trait With Async Methods
Page
Discussion
English
Read
Edit
View history
Tools
Tools
move to sidebar
hide
Actions
Read
Edit
View history
General
What links here
Related changes
Page information
Appearance
move to sidebar
hide
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
First of all, I hope that you are familiar with Rust Trait, a type we use to define shared behavior in an abstract way. For me, I see it as an inferior version the Swift Protocol (I am sorry if you donât agree, but the second we cannot add required properties/fields in trait, it is over)! Anyway! Use async methods with Traits can be inevitable sometimes, but depending on the use case, this can require a bit of trial and error to actually get it to work! In this article, let me share with you really quick on how we can create a trait with async methods, couple problems we might encounter and some workarounds! Basic First of all, defining async functions in a trait does not cause any problems it self! trait Animal { async fn eat(&self, food: &str) -> anyhow::Result<()>; } struct Dog; impl Animal for Dog { async fn eat(&self, food: &str) -> anyhow::Result<()> { println!("eating {}!", food); Ok(()) } } First Problem When do those problems come in? Dynamic Dispatch! PS: If you want to read more about Dynamic Dispatch and Generics, please check out one of my previous articles Rust: Dyn (Dynamic Dispatch) vs Generics (Monomorphism / Static Dispatch)! For example, letâs add a Zoo that contains a field of a vector of Animals! struct Zoo { pub animals: Vec<Box<dyn Animal>>, } And unfortunately, here is what we get. When the trait contains async method, it is not dyn compatible anymore! [[file:Async_1.jpg|700px]] With Async Trait To solve our problem above, we can use this async-trait crate, a crate that provides an attribute macro to make async functions in traits work with dyn traits. All we have to do is to mark our trait and the implementation with async_trait. struct Zoo { pub animals: Vec<Box<dyn Animal>>, } #[async_trait] trait Animal { async fn eat(&self, food: &str) -> anyhow::Result<()>; } struct Dog; #[async_trait] impl Animal for Dog { async fn eat(&self, food: &str) -> anyhow::Result<()> { println!("eating {}!", food); Ok(()) } } Perfect! Letâs add a dummy implementation to the zoo just to confirm. impl Zoo { async fn feed_animals(&self) -> anyhow::Result<()> { for animal in self.animals.iter() { animal.eat("food").await? } Ok(()) } } #[tokio::main] async fn main() -> anyhow::Result<()> { let zoo = Zoo { animals: vec![Box::new(Dog)], }; zoo.feed_animals().await?; Ok(()) } Another Problem However, what if we have a field within the Dog that holds an array of Animal? struct Dog { pub friends: Vec<Box<dyn Animal>>, } [[file:Async_2.jpg|700px]] Oops! There are couple ways of solving this! Approach 1: Box Future First of all, letâs agree on this, async function produce Future. If we can write our function as async, we can convert it to a regular sync fn that returns a Future instead! So! The solution should be pretty straightforward now! Make our function sync and return this BoxFuture from the futures crate! This is actually what async-trait crate does underwood, inserting a BoxFuture and doing the boxing for us. Letâs remove those async_trait attributes and change our trait method as well as the implementation to the following. use futures::future::{ready, BoxFuture}; trait Animal { fn eat(&self, food: &str) -> BoxFuture<'static, anyhow::Result<()>>; } Here, I am using the 'static as my lifetime, but you can also use other lifetimes based on your needs. We can then implement the trait like following. impl Animal for Dog { fn eat(&self, food: &str) -> BoxFuture<'static, anyhow::Result<()>> { println!("eating {}!", food); Box::pin(ok(())) // equivalent to Box::pin(ready(Ok(()))) } } Or if we want to use self and call some other async functions. No problem! impl Animal for Dog { fn eat(&self, food: &str) -> BoxFuture<'static, anyhow::Result<()>> { if let Some(friend) = self.friends.first() { return friend.eat(food); } println!("eating {}!", food); Box::pin(ok(())) // equivalent to Box::pin(ready(Ok(()))) } } In our Zoo, we actually donât have to change anything and just await on that eat exactly the same as above! impl Zoo { async fn feed_animals(&self) -> anyhow::Result<()> { for animal in self.animals.iter() { animal.eat("food").await? } Ok(()) } } Approach 2: Implement Sync The compilerâs message already gave this out, but actually, all we have to do is to implement Sync for our Animal trait! #[async_trait] trait Animal: Sync { async fn eat(&self, food: &str) -> anyhow::Result<()>; } Yes! Thatâs it! By the way, this is also what we need to do if we want to spawn the eat function to some other thread, or we want to join_all for concurrent execution! If you want to find out more about those, please feel free to check out couple of my previous articles! * Rust: Execute Multiple Async Simultaneously: Multithreading vs Futures * Rust: Transfer Data Between Threads Code Snippet Thatâs it for the day! Here are the full snippets for you to give it a try yourself! Async Trait Version use async_trait::async_trait; #[tokio::main] async fn main() -> anyhow::Result<()> { let zoo = Zoo { animals: vec![Box::new(Dog { friends: vec![] })], }; zoo.feed_animals().await?; Ok(()) } struct Zoo { pub animals: Vec<Box<dyn Animal>>, } impl Zoo { async fn feed_animals(&self) -> anyhow::Result<()> { for animal in self.animals.iter() { animal.eat("food").await? } Ok(()) } } #[async_trait] trait Animal: Sync { async fn eat(&self, food: &str) -> anyhow::Result<()>; } struct Dog { pub friends: Vec<Box<dyn Animal>>, } #[async_trait] impl Animal for Dog { async fn eat(&self, food: &str) -> anyhow::Result<()> { println!("eating {}!", food); Ok(()) } } BoxFuture Version use futures::future::{ready, BoxFuture}; #[tokio::main] async fn main() -> anyhow::Result<()> { let zoo = Zoo { animals: vec![Box::new(Dog { friends: vec![] })], }; zoo.feed_animals().await?; Ok(()) } struct Zoo { pub animals: Vec<Box<dyn Animal>>, } impl Zoo { async fn feed_animals(&self) -> anyhow::Result<()> { for animal in self.animals.iter() { animal.eat("food").await? } Ok(()) } } trait Animal { fn eat(&self, food: &str) -> BoxFuture<'static, anyhow::Result<()>>; } struct Dog { pub friends: Vec<Box<dyn Animal>>, } impl Animal for Dog { fn eat(&self, food: &str) -> BoxFuture<'static, anyhow::Result<()>> { if let Some(friend) = self.friends.first() { return friend.eat(food); } println!("eating {}!", food); Box::pin(ok(())) // equivalent to Box::pin(ready(Ok(()))) } } Thank you for reading! Thatâs it for this article! Happy trait-ing! Read the full article here: https://blog.stackademic.com/rust-trait-with-async-methods-a62ce5ad5be8
Summary:
Please note that all contributions to JOHNWICK may be edited, altered, or removed by other contributors. If you do not want your writing to be edited mercilessly, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see
JOHNWICK:Copyrights
for details).
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Search
Search
Editing
Rust: Trait With Async Methods
Add topic