Rust async/await with Trait
2021-07-24
RustRust supports asynchronous programming with async
and await
syntax since Rust 1.39.0.
https://blog.rust-lang.org/2019/11/07/Rust-1.39.0.html
Asynchronous Programming in Rust would be the best material to learn asynchronous programming in Rust.
This post describes not asynchronous programming but how to use async
with Trait, which was not easy as I expected.
What the problem is
I expected this code works.
trait MyTrait {
async fn my_function(&self, test: &str) -> Result<String, MyError>;
}
struct MyError {
message: String,
}
However, the result of cargo build
is the following:
$ RUSTFLAGS=-Awarnings cargo build
...(snip)...
error: expected one of `!`, `+`, `::`, `;`, `where`, or `{`, found `}`
--> src/main.rs:3:1
|
2 | async fn my_function(&self, test: &str) -> Result<String, MyError>
| ----------- while parsing this `fn` - expected one of `!`, `+`, `::`, `;`, `where`, or `{`
3 | }
| ^ unexpected token
error[E0706]: functions in traits cannot be declared `async`
--> src/main.rs:2:5
|
2 | async fn my_function(&self, test: &str) -> Result<String, MyError>
| -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| `async` because of this
|
= note: `async` trait functions are not currently supported
= note: consider using the `async-trait` crate: https://crates.io/crates/async-trait
...
Obviously Rust doesn't allow using async
within trait
definitions.
Instead, as the error message says, we can use async-trait crate to add async fn
support in traits.
This crate is so easy to use that I couldn't be aware what's happening behind the scene.
How to use async fn in traits without async-trait
In short,
trait MyTrait {
fn my_function(&self, test: &str) -> Pin<Box<dyn Future<Output = Result<String, MyError>> + Send>>;
}
Then,
$ cat ./src/main.rs
use std::{future::Future, pin::Pin};
trait MyTrait {
fn my_function(&self, test: &str) -> Pin<Box<dyn Future<Output = Result<String, MyError>> + Send>>;
}
struct MyError {
message: String,
}
$ RUSTFLAGS=-Awarnings cargo build
Finished dev [unoptimized + debuginfo] target(s) in 0.13s
Yes!
More Realistic Example
A typical use-case of asynchronous programming is sending HTTP requests. In this example, use seanmonstar/reqwest, which is very handy for dealing with asynchronous HTTP requests on Tokio.
This code snippet has only codes relate to async/await, so if you're interested in full codes, you can see it at petitviolet/rustranslator.
use std::{future::Future, pin::Pin};
#[derive(Debug)]
struct TranslateError {
message: String,
}
impl TranslateError {
pub fn new(msg: String) -> Self {
Self { message: msg }
}
}
type TranslateResult = Result<String, TranslateError>;
trait Translator {
fn translate(
&self,
text: &str,
from: &Lang,
to: &Lang,
) -> Pin<Box<dyn Future<Output = TranslateResult> + Send>>;
}
struct Google { /* */ }
struct GoogleRequestBody { /* */ }
struct GoogleResponseBody { /* */ }
impl Translator for Google {
// equivalent to `async fn translate(&self, text: &str, from: &Lang, to: &Lang) -> TranslateResult {`
fn translate(
&self,
text: &str,
from: &Lang,
to: &Lang,
) -> Pin<Box<dyn Future<Output = TranslateResult> + Send>> {
let body = GoogleRequestBody::new(text, from, to);
let client = reqwest::Client::new();
let req = client
.post(self.translate_text_url())
.header("Authorization", format!("Bearer {}", &self.access_token))
.json(&body)
.send();
let res = async move {
req.await
.unwrap()
.json()
.await
.map(|res: GoogleResponseBody| res.text())
.map_err(|err| TranslateError::new(format!("error! {:#?}", err)))
};
Box::pin(res)
}
}
As you see, the signature of a function of a trait looks weird. The output type is Pin<Box<dyn Future<Output = T> + Send>>
.
This method signature is explained in official compiler error index page as E0706.