Why asynchronous Rust doesn't work

This page summarizes the projects mentioned and recommended in the original post on news.ycombinator.com

Our great sponsors
  • InfluxDB - Power Real-Time Data Analytics at Scale
  • WorkOS - The modern identity platform for B2B SaaS
  • SaaSHub - Software Alternatives and Reviews
  • rust

    Empowering everyone to build reliable and efficient software.

    A bigger problem in my opinion is that Rust has chose to follow poll-based API (you can say that it was designed around epoll), while completion-based APIs (e.g. io-uring and IOCP) probably will be prevalent way of doing async in future.

    Instead of carefully weighing advantages and disadvantages of both models, the decision was effectively made on the ground of "we want to ship async support as soon as possible" [1]. Unfortunately, because of this rush, Rust got stuck with a poll-based model with a whole bunch of problems without a clear solution in sight (async drop anyone?). And instead of a proper solution for self-referencing structs (yes, a really hard problem), we did end up with the hack-ish Pin solution, which has already caused a number of problems since stabilization and now may block enabling of noalias by default [2].

    Many believe that Rust async story was unnecessarily rushed. While it may have helped to increase Rust adoption in the mid term, I believe it will cause serious issues in the longer term.

    [1]: https://github.com/rust-lang/rust/issues/62149#issuecomment-...

  • monadless

    Syntactic sugar for monad composition in Scala

    > If anything, async-await feels like an extremely non-functional thing to begin with

    It, like many other things, forms a monad. In fact async-await is a specialization of various monad syntactic sugars that try to eliminate long callback chains.

    Hence things like Haskell's do-notation are direct precursors to async-await (some libraries such as Scala's monadless https://github.com/monadless/monadless make it even more explicit, there lift and unlift are exactly generalized versions of async and await).

  • InfluxDB

    Power Real-Time Data Analytics at Scale. Get real-time insights from all types of time series data with InfluxDB. Ingest, query, and analyze billions of data points in real-time with unbounded cardinality.

  • reqwest

    An easy and powerful Rust HTTP Client

    It's impossible that a beginner attempting to use the language to do anything related to networking won't be guided into the async nightmare before they have even come to grips with the borrow checker.

    1. https://crates.io/crates/reqwest

  • ureq

    A simple, safe HTTP client

  • blog-comments

    Comments for the blog at theta.eu.org.

    This was a great article, very easy to understand without leaving out the fundamental pieces (the saga that is async implementation difficulty). I think I can even boil this situation down to Rust having it's Monad moment.

    It's in this (HN) comment thread and a bunch of informed comments on the original article:

    https://github.com/eeeeeta/blog-comments/issues/10#issuecomm...

    https://github.com/eeeeeta/blog-comments/issues/10#issuecomm...

    https://github.com/eeeeeta/blog-comments/issues/10#issuecomm...

    The comments are correct in my view -- Rust can't/shouldn't do what Haskell did, which was to create use a general purpose abstraction that is essentially able to carry "the world" (as state) along with easy-to-trade chained functions on that state (whenever it gets realized). Haskell might have solved the problem, but it has paid a large price in language difficulty (perceived or actual) because of it, not mentioning the structural limitations of what Rust can do and it's constraints. The trade-off just isn't worth it for the kind of language Rust aims to be.

    Realistically, I think this issue is big but not enough to write off rust for me personally (as the author seems to have) -- I'd just do the move + Arc shenanigans because if you've been building java applications with IoC and/or other patterns that generally require global-ish singletons (given example was a DB being used by a server), this isn't the worst complexity trade-off you've had to make, though the Rust compiler is a lot more ambitious, and Rust has a cleaner, better, more concise type system as far as I'm concerned.

    I think another thing I've gained from this article is another nice little case where Haskell (if you've taken the time to grok it sufficiently, which can be a long time) offers a bit of a nicer general solution than Rust, assuming you were in a world where those two were actually even substitutes. In the much more likely world where you might compare Rust and Go2, this might be a win for Go2, but the rest of the language features would put rust on top for me.

  • hyper

    An HTTP library for Rust (by hyperium)

    I've jumped on the Rust bandwagon as part of ZeroTier 2.0 (not rewriting its core, but rewriting some service stuff in Rust and considering the core eventually). I've used a bit of async and while it's not as easy as Go (nothing is!) it's pretty damn ingenious for a systems programming language.

    Ownership semantics are hairy in Rust and require some forethought, but that's also true in C and C++ and in those languages if you get it wrong there you just blow your foot off. Rust instead tells you that the footgun is dangerously close to going off and more or less prohibits you from doing really dangerous things.

    My opinion on Rust async is that it its warts are as much the fault of libraries as they are of the language itself. Async libraries are overly clever, falling into the trap of favoring code brevity over code clarity. I would rather have them force me to write just a little more boilerplate but have a clearer idea of what's going on than to rely on magic voodoo closure tricks like:

    https://github.com/hyperium/hyper/issues/2446

    I'd rather have seen this implemented with traits and interfaces. Yes it would force me to write something like a "factory," but I would have spent 30 minutes doing that instead of three hours figuring out how the fuck make_service_fn() and service_fn() are supposed to be used and how to get a f'ing Arc<> in there.

    Oh, and Arc<> gets around a lot of issues in Rust. It's not as zero-cost as Rc<> and Box<> and friends but the cost is really low. While async workers are not threads, it can make things easier to treat them that way and use Arc<> with them (as long as you avoid cyclic structures). So if async ownership is really giving you headaches try chickening out and using Arc<>. It costs very very little CPU/RAM and if it saves you hours of coding it's worth it.

    Oh, and to remind people: this is a systems language designed to replace C/C++, not a higher level language, and I don't expect it to ever be as simple and productive as Go or as YOLO as JavaScript. I love Go too but it's not a systems language and it imposes costs and constraints that are really problematic when trying to write (in my case) a network virtualization service that's shooting (in v2.0) for tens of gigabits performance on big machines.

  • rupy

    HTTP App. Server and JSON DB - Shared Parallel (Atomic) & Distributed

    Well I don't know the details, but if you read my comment below you'll see that there might be a good reason to use a complex memory model VM with GC.

    I can't explain it, just claim that my code works and performs accordingly: https://github.com/tinspin/rupy

  • WorkOS

    The modern identity platform for B2B SaaS. The APIs are flexible and easy-to-use, supporting authentication, user identity, and complex enterprise features like SSO and SCIM provisioning.

  • async-trait

    Type erasure for async trait methods

    This macro goes a very long way toward solving the problem: https://github.com/dtolnay/async-trait

NOTE: The number of mentions on this list indicates mentions on common posts plus user suggested alternatives. Hence, a higher number means a more popular project.

Suggest a related project

Related posts