Confused about async/.await?, async-std, tokio

Hi all,

I'm about to dive in the async/await Rust land, but I'm a little bit confused about the different options in front of me.

Could someone explain the differences between:

  • async/await? keywords
  • tokio
  • async-std crate

I can understand async/await? keywords, but which library should I use ? Are there any other options I missed ?

Thanks for your explanation.

1 Like

The async keyword is used to create an async function/block, inside which you can use the await keyword. The await keyword is necessary to call a function marked async, so the async-ness of a function is propagated to the caller.

Since the async-ness is propagated like that, but main is sync, you will eventually need some way to go from sync to async. This is the purpose of a runtime, which is a library that can execute async code. Tokio and async-std are two such libraries that provide a runtime (there is no runtime in the standard library).

Tokio and async-std are relatively similar to each other and serve the same purpose. I recommend Tokio since it is more widely used and has a larger ecosystem.

As for the futures crate, it is a crate with various utilities useful when writing asynchronous code.

1 Like

I have just traveled this road, so I am still a bit of a noob myself at this, but…
async marks a function or code block as a coroutine rather than a function or normal bit of code. Coroutines are bits of code that are executed by an event loop or threadpool. Rust implements coroutines, event loops and threadpools by using futures and executors backed by threads. Since async is part of the language there have to be futures as part of the standard library. However these are intended only for implementing the coroutines, not really for application or library use. The exception is the futures crates which is futures intended for use by the rest of us.
In a coroutine you can use .await which acts on a future saying "wait til the future is ready, but do not block, allow other coroutines in this event loop/threadpool/executor to execute. It is the yield of other coroutine systems.
async-std is a library built over async/.await and the futures crates designed to bring asynchronous I/O in a minimalist way to Rust. It also support HTTP and HTTPS and thus lots of Web stuff.
I believe the tokio crate predates async/.await (and indeed async-std, but I am not sure) and brought asynchronous I/O, particularly HTTP/HTTPS Web-y stuff to Rust before it got into Rust itself. It has now evolved to integrate all the async/.await and futures stuff.
It seemed to me that tokio has more traction than async-std possibly due to aving been around longer and being more focused on Web-y stuff. I looked at it and then async-std and went with async-std for my application since I needed TCP but very minimally, with no HTTP/HTTPS (I am doing real networking :slight_smile: ) and async-std seemed to be a better fit for what I needed. Actually what I needed was networking over the GTK+ event loop and part of my application uses that. However I found async-std a useful tool for a bit where gtk-rs currently doesn't have the right bits.
I have been very happy with my experience of async-std, but have no experience of tokio to compare. Comparing with gtk-rs is not fair as yet since the right bits of code are not yet integrated. All in all the Rust asynchronous I/O learning curve has been relatively straightforward, with async-std providing a very nice expression of intention. Marking functions and blocks with async to create coroutines and using .await is very easy and once you appreciate this is coroutines and not sequential code, you must make the shift of mindset, development become really rather straightforward and fun.

2 Likes

Hi @Russel, @alice

Thanks for those detailed answers. It gives me a better idea of where I can head now.

My main target for async I/O is not network but rather file I/O. I was wondering whether it could be a speed improvement when reading a file and looking for some regexes' matches for each line of the file.

Async/await provides no advantage for file IO because the OSes don't provide any apis for working with files asynchronously, meaning that Tokio and async-std just use blocking file IO on a thread pool.

Using an ordinary thread pool will give better performance.

2 Likes

I don't understand your answer. NodeJS provides both async and sync file I/O methods, and Linux provides the epoll core API.

The epoll api is not usable for file IO, as epoll always reports a file as "ready" even though trying to read from it will block. The NodeJS async File api likely works like Tokio's, namely that the operation is scheduled to run on a separate thread pool, with the result sent back to the thread that started it afterwards.

4 Likes

It seems the new io_uring model will allow it: Is there really no asynchronous block I/O on Linux? - Stack Overflow

1 Like

Yes, if you have a very new Linux kernel. There are not yet any async runtimes that support io_uring. We are working on it in Tokio, but it's a very different model from epoll and it is not easy to integrate.

2 Likes

Yep :wink:
Thanks for your comments anyway :+1:

io_uring is a completion-based I/O API (like Windows I/O Completion Ports), which is not currently supported by async/await. Boats wrote an blog about this recently. Notes on io-uring

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.