Popol: Minimal non-blocking I/O with Rust

submited by
Style Pass
2021-05-30 14:30:05

Asynchronous I/O in Rust is still very much in its infancy. Async/await, Rust’s solution to the problem, was recently stabilized and so when came the time to implement some peer-to-peer networking code, I reached for the shiny new feature. To my dismay, it created more problems than it solved. Indeed, I quickly regretted going down that path and searched for alternatives. All I was looking for was an easy way to handle between fifty and a hundred TCP connections (net::TcpStream) efficiently to implement the reactor for nakamoto, a Bitcoin client I’ve been working on.

The crux of the problem with async/await is that it is incompatible with the standard library traits, such as Read and Write. Partly, this is due to the design decisions behind Rust’s async implementation: the choice of cooperative multitasking instead of preemptive multitasking, for instance. Languages built on the latter don’t suffer from this scission. Good examples include Haskell and Erlang, which don’t have a language-level concept of “async” – the implementation is transparent to the user.

As it stands, the async/await system in Rust comes with an incompatible set of traits: AsyncRead and AsyncWrite, which don’t play nicely with the standard library. Channels, for example the amazing crossbeam-channel don’t work in asynchronous code – you need an async variant. Alternatively, most of the runtimes that effectively provide replacement types for channels, files and sockets, such as async-std and tokio, have a large dependency footprint, inherent in the complexity of the problem. For example, smol, one of the “small” runtimes still consists of about 24 crates, including its transitive dependencies, as of today.

Leave a Comment