Live data from Hacker News

Why asynchronous Rust doesn't work

theta.eu.org

111–120 of 499 posts

Re: Why asynchronous Rust doesn't work

#111
In order to understand why async Rust is difficult to use today, one must understand the background and goals of Rust, in particular Rust's zero-cost abstractions:

The type- and trait system got Rust very, very far, delivering static dispatch and stack allocations by default, while maintaining type safety and a high level of flexibility for a manageable complexity cost. When async Rust was incubating, these goals were upheld dogmatically, and the community set out to preserve all these traits when building async, for better and worse. However, in order to get there, Rust needed to:

- Power-use existing complex features and/or verbose wrappers such `Box`, `Send`, `Arc`, `impl Trait` types and an influx in generic type parameters. (Several of these cause function-coloring issues)

- Introduce new complexity for self-referential structs with `Pin` (and if you dare - check out "pin projection")

- Allow for custom and extensible runtimes (aka executors - responsible for scheduling), including both single- and multithreaded.

The end result is a tremendous technological achievement - essentially all requirements were upheld (to the point where async can be used even in embedded environments). However, for non-experts, it's tremendously complex - which affects almost all users, even those that don't write custom runtimes or advanced combinators.

I'm not the one to judge whether the story of async Rust is a failure, but if it is, there is not a single reason. In broad terms, the project was extremely ambitious and Rust, arguably, wasn't entirely ready for it. A couple of missing features that could possibly have helped:

- Generic Associated Types (currently causes verbose workarounds for combinators, streams etc)

- "first-class" / ergonomic support for state machines (async fns compiles to a state-machine - relevant also for async traits)

- Reliable destructors (`drop` is famously not guaranteed to run in safe Rust, preventing things like borrowing across tasks, which exacerbates the `Arc`-hell)

Additionally, async I/O kernel interfaces were maturing in parallel - e.g. io_uring was released after async Rust was stabilized (afaik), which might have resulted in a different, completion-based design.

Re: Why asynchronous Rust doesn't work

#112
> projects that depend on like 3 different versions of tokio and futures

I ended up here recently.

This part is truly awful. I can forgive a lot, and much of what appears in this blog post falls under than umbrella, but the tokio/std futures schism is offensively discouraging. I strongly suspect it is a symptom of some great dysfunction.

Re: Why asynchronous Rust doesn't work

#113
post #2

I'm coming around to the position that pure "async" is OK, and pure threading is OK, and green threads (as in Go goroutines) are OK, but having more than one of those in a language is not OK. They do not get along well.

Well I'd say you have to take a lot of care to make them compose well.

Just like it takes care to compose thread- and process-based concurrency, it takes care to compose threads and async correctly and comprehensibly.

If you care about utilization you MUST use threads (or processes, which are a different story). So if you use async, then you have the problem of composing them, which is indeed hard. There are some C++ codebases that do this well but they're notable for being exceptions ...

There does appear to be overuse of async as a premature optimization. The default should to use threads and explicitly shared state -- basically the idioms in Go, although I'm sure ownership complicates that a lot.

Re: Why asynchronous Rust doesn't work

#114
post #83
post #76

Earlier quoted context omitted.

I really don't see a problem with function color in a typed language. Every function has color and compiler enforces you pass right color. Async is just another type that may have some convenient syntax. It might be inconvenient in untyped languages like js since you couldn't compose them.

It’s not a problem of being error-prone, the problem is that the ecosystem is split. As a crate author, do I implement my functions for color A or color B? Every crate author will eventually encounter this question and the inevitable fall out from making the wrong decision. Presently, the only way to deal with this is implement your functionality in both colors.

It's only a question if the function needs to call something else that could be either color. The vast majority of code does not.

It would take a couple months of bike shedding syntax and discussing internals but I feel like there's a version of this (applied to other function colors) that allowed a library author to annotate/decorate a function for an inclusive or exclusive set of these decorations the compiler can pick to call from a calling context, and allow the function author to specialize over the combination of decorators that actually get called. Since the set of decorators is small (I can only think of 2-3, including async) this feature would be straightforward to implement albeit potentially verbose.

There's a fancy compiler pass to make async work though. You need a special control flow analysis to figure out yield points in async bodies, since this new syntax would have no await.

It's probably too late for that. It might be implemented in terms of HKT.

Re: Why asynchronous Rust doesn't work

#115

Earlier quoted context omitted.

> If anything, async-await feels like an extremely non-functional thing to begin with Futures/promises (they mean different things in different languages), like many other things, form monads. In fact async-await is a specialization of various monad syntactic sugars that try to eliminate long callback chains that commonly affect many different sorts of monads. Hence things like Haskell's do-notation are direct precur…

I'm not clear on if this is supposed to be disagreement or elaboration or education. The fact that in a language like Haskell, you can perform something like async-await with futures (which are absolutely a kind of monad) in a natural way is precisely what I had in mind with what you quoted. Regardless, the specific heritage of async-await syntax seems rooted in procedural languages (that do borrow much else as well…

You don't need async/await to do monadic comprehension in Scala, it's built into the language from the very beginning with `for`.

This was inspired by do notation, which came about ~1998.

Re: Why asynchronous Rust doesn't work

#116
I never see popol [1] mentioned in these discussions:

    Popol is designed as a minimal ergonomic wrapper
    around poll, built for use cases such as peer-to-peer
    networking, where you typically have no more than a
    few hundred concurrent connections. It’s meant to be
    familiar enough for those with experience using mio,
    but a little easier to use, and a lot smaller.
It shows a lot of promise.

[1]: https://cloudhead.io/popol/

Re: Why asynchronous Rust doesn't work

#117

A bigger problem in my opinion is that Rust has chosen to follow the poll-based model (you can say that it was effectively designed around epoll), while the completion-based one (e.g. io-uring and IOCP) with high probability will be the way of doing async in future (especially in the light of Spectre and Meltdown). Instead of carefully weighing advantages and disadvantages of both models, the decision was effectively…

I agree that Rust async is currently in a somewhat awkward state. Don't get me wrong, it's usable and many projects use it to great effect. But there are a few important features like async trait methods (blocked by HKT), async closures, async drop, and (potentially) existential types, that seem to linger. The unresolved problems around Pin are the most worrying aspect. The ecosystem is somewhat fractured, partially…

>A completion model would require a heavier, standardized runtime and associated inefficiencies like extra allocations and indirection, and prevent efficiencies that emerge with polling.

You are not the first person who uses such arguments, but I don't see why they would be true. In my understanding both models would use approximately the same FSMs, but which would interact differently with a runtime (i.e. instead of registering a waker, you would register an operation on a buffer which is part of the task state). Maybe I am missing something, so please correct me if I am wrong in a reply to this comment: https://news.ycombinator.com/item?id=26407824

Re: Why asynchronous Rust doesn't work

#118
post #24

The author admits that they needed Arc/clone in a footnote. So I think the more interesting title would be to rehash/interpret this as “Why asynchronous Rust is too hard”. Having played with this a bit recently, I think folks are going to end up: - assuming Tokio (now that it’s 1.xx) - mark nearly everything as async - push callers to use rt.block_on to wait for an async function from non-async code I definitely miss…

I noticed this a few months ago as well. I was looking for a way to do simple synchronous HTTPS requests in a CLI, and it seemed like using `reqwest` with tokio was more or less the default option in Rust. I ended up using ureq, and it looked like there may have even been ways to avoid it using hyper and an alternative tls-connector but the simplest solution seemed to be "just use tokio".

When something foundational, like networking, gains a defacto dependency on tokio, it seems inevitable that a large footprint of the ecosystem will eventually depend on it.

Re: Why asynchronous Rust doesn't work

#119
post #96

Earlier quoted context omitted.

I did read the article; it's overly focused on async (especially the headline). The body quite correctly analyses the problem with functions, but misses that this is much more general than async; just adopting a different async model isn't a solution.

Well, the article’s focus is on asynchrony. Naturally then, it talks about closures within the context of asynchrony and to the extent that it is relevant. The author could have made the topic about closures, but that’s not what they wanted to talk about.

The author's focus on async leads them to the wrong conclusion. Not adopting async would not have solved the fundamental underlying problem; the perennial issues with error handling in Rust are another manifestation of the same problem, and as soon as people start trying to do things like database transaction management they'll hit the same problem again. Ripping async out of Rust isn't a solution; this problem will come up again and again unless and until Rust implements proper first-class functions.

Re: Why asynchronous Rust doesn't work

#120
post #55
post #22

I find rust to be too hard to use for it to ever become huge

Just turn all &str to String and use .clone() liberally while you get used to the language, and you'll avoid having to think about the borrow checker for most basic applications. I think Haskell is stuck like you say because if your code is overly wordy, it will run slowly too, yet Rust is still relatively fast even if you do .clone() a lot.

Ah maybe that's how I should approach. I was definitely interested in Rust as as personal replacement for C or C++
Post reply on HN