In the same spirit of the async-await experience reports thread, I thought I'd document my experiences trying to implement a very simple async program, but focus on the ecosystem (and not the details of the async/await syntax). The program has the following behavior: Wait for input from the user on stdin, and if nothing is received in 10 seconds, exit the program.
For this exercise, I'm using the latest nightly compiler (rustc 1.38.0-nightly (60960a260 2019-08-12)
)
Tokio
I started with tokio, because it's a very familiar name. I'm using the latest published crate (0.2.0-alpha.1).
Tokio has both a Stdin
and a Timeout
type that look perfect for what I want, and I discovered them quickly. The code was easy to write, and I think easy to read:
#[tokio::main]
async fn main() {
let mut stdin = tokio::io::stdin();
let mut s = String::new();
let mut read_stdin = tokio::timer::Timeout::new(stdin.read_to_string(&mut s), std::time::Duration::from_secs(10));
match read_stdin.await {
Ok(data) => println!("Read data: {:?}", data),
Err(e) => println!("Timeout: {:?}", e)
}
}
Unfortunately this doesn't execute as expected, as the future always returns an error:
Read data: Err(Custom { kind: Other, error: "
blocking
annotated I/O must be called from the context of the Tokio runtime." })
This seems to be a known issue.
On the documentation side, the methods like read_to_string
are part of the AsyncReadExt
trait, but rustdoc seems to struggle here: Clicking on name of the trait hyperlink seems to lead to some different unnamed trait that isn't what a want. And expanding the trait docs inline from the Stdin page show that the read_to_string
method returns some opaque type ReadToString
. This type is not hyperlinked, and so I have no idea that this thing is actually a future (and is thus no idea that i can use await
with it, and also no idea what type it yields).
Runtime
Next, I tried to use the runtime
crate (v0.3.0-alpha.6), and its default native executor, but unfortunately this crate does not yet have support for either stdio streams nor timers (this is mentioned midway down the README for the crate).
Others
Tokio and Runtime were the only two runtime crates that I knew of without searching. So I did a little searching on crates.io for other things to try. I honestly didn't expect this to be very helpful (I've never had the skills to use crates.io as a discovery tool), and indeed I found the results not very helpful. There were a lot of creates with promising, but generic names, such as:
- futures-async-runtime
- futures-async-runtime-preview
- async-runtime
- runtime-async
- runtime
- runtime-tokio
The first looked the most promising (I did not try the others). Its documentation link from crates.io was broken, but after some clicking I landed upon something useful. I could not find an annotation that could be used to turn fn main()
into an async function, so I quickly tried to write some old-style futures code. I got something to work that would read from stdin, but could not find any timers:
async fn read_from_stdin() -> Result<String, Box<dyn Error>> {
let mut stdin = futures::io::AllowStdIo::new(std::io::stdin());
let mut s = String::new();
let len = stdin.read_to_string(&mut s).await?;
Ok(s[..len].to_owned())
}
fn main() {
let runner = read_from_stdin().then(|s| {
println!("data from stdin: {:?}", s);
futures::future::ok::<(), ()>(())
});
ThreadPool::new().unwrap().run(runner);
}
This probably took me about 30 minutes to figure out how to write due to some stalls, but this isn't a support forum, so there's no need to get into that now.
Documentation was pretty solid, but it's not easy to hold all the types in your head (Futures, FuturesExt, AsyncRead, ASyncReadExt, AllowIO, etc). There was a lot of flipping between browser tabs. I did notice that this crate has a "async-await" feature, but to be honest it wasn't clear to me what I needed it for (the above code compiles without it).
Summary
I was pretty excited when writing the tokio version due to how straightforward the code looked. But it was disappointing to not have ended up with any working code. I thought briefly about trying to roll my own runtime, but that felt way over my head (and beyond the scope of what I initially set out to do).
I skimmed the rust async book to see if it had any suggestions about what other runtimes to try, but it doesn't yet have anything to say here.
I still remain excited about async in rust, but feel like I need to wait a few more months for the ecosystem to continue to build out and up