Async-await ecosystem experience report

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:

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 :rocket:

10 Likes

Just want to give you some feedback:

  • tokio is currently being rewritten for std futures, so it's not at all stable
  • stdin is tricky in async because it's a global handle in the entire process and normally a blocking io, but there is examples of how to do it with channels, like this one and more here.
  • as for timers, there is futures-timer
  • as for runtimes, the runtime crate should work fine, but pulls in network dependencies, otherwise I wrote an async_runtime which is more barebones, just spawning futures to avoid mixing it with network dependencies and without the need to box the futures.
4 Likes

Thanks for the hints. I'll try to wire up runtime + futures-timer tonight to see how that goes.

I just realized that I omitted your own crate in my list of crate search results. That was definitely unintentional, and I'm sorry about that. I'll also give your crate a try, too!

@eminince np, the main difference for you here will be that your crate doesn't need to pull network dependencies as you don't seem to do networking.

oh, and that if you want you can spawn futures on a LocalPool (not threadpool) if you don't need threads.

Just discovered yet another timers crate:

And some discussion about it:

Haven't got any experience with either crate.

Would be pretty cool to add this (another?) list of crates to the async book
Even better with some examples

Internet is full of .1 tutorials
Uber confusing

I was thinking this too. At some point before stabilization it would be good if there was a blog post giving a good overview of all the crates that are up to date with async-await going from basic (runtimes) to more specific (some specific functionality, but which has an async API).

Maybe that's a good thing for areweasyncyet.rs?

1 Like

Would it be appropriate for the rust async book to make specific recommendations about what crates to use? Or would that be too opinionated for an official rust book?

In my opinion this is more something for a blog post than for the async book. The async book has enough on it's plate covering all ecosystem independent aspects of the language.

Also because the ecosystem is by definition a moving target, and so after 6 months whatever we write now is bound to be outdated.

batteries not included/futures not usable w/o a runtime
=> a semi-official list of recommended runtime crates is needed

being on github book can be updated too?
could it have chapters like "Runtimes Overview Sep 2019"?

I have OS based timers crate of my own https://github.com/DoumanAsh/async-timer

It is more militaristic