Earlier quoted context omitted.
Asynchronous nonblocking interfaces are more general-purpose than synchronous blocking interfaces. I can't speak for this library or Rust specifically, but in my experience well-designed asynchronous libraries allow you to interact with them in a synchronous style as well, if you wish. Netty is an asynchronous, event-driven network framework for Java, and it's perfectly possible to expose synchronous blocking abstrac…
One is not more powerful than the other: they are considered "duals" and this has been proven in the literature (back in 1978, no less); most of the supposed downsides of threads are due to people assuming a specific implementation of threads (many if not most of which suck). Here are some papers that would normally be assigned reading in a graduate level Computer Science course in Operating Systems as background ref…
Consider: in calling conventions, the continuation passing style is dual to the "direct" calling convention (i.e., call stack with return values); the call-by-name style is dual to call-by-value style; Lambda Calculus and Turing Machines are dual in their ability to compute all effectively calculable functions.
These dualities do not mean it's equally practical to build systems in both ways. Sometimes one approach ends up being more practically useful.
Most programmers prefer to use the direct calling convention, and find complex continuation passing style to be difficult to read and maintain. JavaScript programmers may be familiar with the pain of CPS due to excessive use of callbacks (not strictly CPS but has similar drawbacks). Similarly, writing code purely in call-by-name style can be confusing and have difficult to predict performance impacts (e.g., Haskell lazy evaluation semantics).
In their article the article "On the Duality of Operating System Structures", Lauer and Needham present a similar conclusion [3]:
> "The principal conclusion we will draw from these observations is that the considerations for choosing which model to adopt in a given system [...] [are] a function of which set of primitive operations and mechanisms are easier to build or better suited to the constraints imposed by the machine architecture and hardware."
In that passage they are describing message passing vs. procedure call systems, and I interpret this to be their acknowledgment that, though the systems are dual, one architecture or another is more appropriate in certain circumstances.
Getting back to our original topic: this thread was about the decision of a Rust library to offer async or sync IO as its choice of primary primitive. I think async is the better general-purpose choice, because it's clean, simply, and straightforward to expose a synchronous interface on top of an async interfaces with futures; and the other way around is messy and difficult.
Can you elaborate on the lightweight co-routine library that can be used to convert anything synchronous into async? I'm curious about that, because Rust previously had support for coroutines (green threads), and decided to remove them due to a number of problems [1]. Meanwhile, Rust developers were able to devise a zero-cost futures abstraction on top of asynchronous IO [2]. Unlikely the problematic green threads strategy, this approach doesn't impose any complicated constraints on the systems that use it (FFI requirements), and doesn't add runtime overhead.
What co-routine library would you recommend that avoids the downsides in [1]?
[1] https://github.com/aturon/rfcs/blob/remove-runtime/active/00... describes some pretty tricky challenges.
[2] https://aturon.github.io/blog/2016/08/11/futures/
[3] https://pdfs.semanticscholar.org/2948/a0d014852ba47dd115fcc7...