Live data from Hacker News

Why asynchronous Rust doesn't work

theta.eu.org

61–70 of 499 posts

Re: Why asynchronous Rust doesn't work

#61

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…

Since you clearly have expertise, I'm curious if you might provide some insight into what would roughly be different in an async completion-based model & why that might be at a fundamental odds with the event-based one? Like is it an incompatibility with the runtime or does it change the actual semantics of async/await in a fundamental way to the point where you can't just swap out the runtime & reuse existing async…

I'm also curious about this. Boats wrote some about rust async and io-uring a while ago that's interesting[1], but also points out a very clear path forward that's not actually outside the framework of rust's Future or async implementation: using interfaces that treat the kernel as the owner of the buffers being read into/out of, and that seems in line with my expectations of what should work for this.

But I haven't touched IOCP in nearly 20 years and haven't gotten into io-uring yet, so maybe I'm missing something.

Really the biggest problem might be that switching out backends is currently very difficult in rust, even the 0.x to 1.x jump of tokio is painful. Switching from Async[Reader|Writer] to AsyncBuf[Reader|Writer] might be even harder.

[1] https://boats.gitlab.io/blog/post/io-uring/

Re: Why asynchronous Rust doesn't work

#62
post #53

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…

Could Rust switch? More importantly, would a completion based model alleviate the problems mentioned?

Without introducing Rust 2? Highly unlikely.

I should have worded my message more carefully. Completion-based model is not a silver bullet which would magically solve all problems (though I think it would help a bit with the async Drop problem). The problem is that Rust async was rushed without careful deliberation, which causes a number of problems without a clear solution in sight.

Re: Why asynchronous Rust doesn't work

#63
post #23

Earlier quoted context omitted.

> So... don’t use first-class functions so much? It’s a systems language, not a functional language for describing algorithms in CS whitepapers. Then maybe it was a mistake to adopt an async paradigm from functional languages that relies heavily on the idea that first-class functions are cheap and easy? (FWIW I think Rust was right to pick Scala-style async; it's really the only nice way of working with async that I'…

> maybe it was a mistake to adopt an async paradigm from functional languages > I think Rust was right to pick Scala-style async I'm confused by this assertion. I'm more aware of procedural language origins of syntactic async/await than functional? The scala proposal in 2016 for async/await even cites C#'s design (which came in C# 5.0 in 2012) as an inspiration[1]. From there, it appears python and typescript added t…

> 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 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).

To see how async-await might be generalized, one could turn to various other specializations of the same syntax, e.g. an async that denotes a random variable and an await that draws once from the random variable.

To see the correspondence with a flatMap method (which is the main component of a monad), it's enough to look at the equivalent callback-heavy code and see that it looks something like

  Future(5)
    .flatMap(x ->
      doSomethingFutureyWithX(x)
        .flatMap(lookItsAnotherCallback)
    )

Re: Why asynchronous Rust doesn't work

#64

Earlier quoted context omitted.

Correct me if I'm wrong, but isnt any sort of async support non integral to rust? For example in something like Javscript you can't impliment your own async. But in C, C++ or Rust you can do pretty much anything you want. So if in the future io-uring and friends become the standard can't that just be a library you could then use? Similar to how in C you don't need the standard library to do threads or async.

Yes, you can encode state machines manually, but it will be FAR less ergonomic than the async syntax. Rust has started with a library-based approach, but it was... not great. Async code was littered with and_then methods and it was really close to the infamous JS callback hell. The ergonomic improvements which async/await brings is essentially a raison d'être for incorporating this functionality into the language.

Interesting!

For comparison, Haskell went with the library approach but has the syntactic sugar of the equivalent of `and_then` built into the language. (I am talking about Monads and do-notation.)

It's a bit like iterating in Python: for-loops are a convenient syntactic sugar to something that can be provided by a library.

Re: Why asynchronous Rust doesn't work

#65
post #17

This article is a bit confusing. The concrete issues described all relate to closures, but the author says the real problem is async and they liked Rust before that (when it had the closures they have concrete issues with?).

He says the problem is the proliferation of async libraries, which forces their use and pollute everything they touch in association with the closure problem. It's the combination of both.

Re: Why asynchronous Rust doesn't work

#66

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…

Correct me if I'm wrong, but isnt any sort of async support non integral to rust? For example in something like Javscript you can't impliment your own async. But in C, C++ or Rust you can do pretty much anything you want. So if in the future io-uring and friends become the standard can't that just be a library you could then use? Similar to how in C you don't need the standard library to do threads or async.

I agree. Completion-based APIs are more high level, and not a good abstraction at the systems language level. IOCP and io_uring use poll-based interfaces internally. In io_uring's case, the interfaces are basically the same ones available in user space. In Windows case IOCP uses interfaces that are private, but some projects have figured out the details well enough to implement decent epoll and kqueue compatibility libraries.

Application developers of course want much higher level interfaces. They don't want to do a series of reads; they want "fetch_url". But if "fetch_url" is the lowest-level API available, good luck implementing an efficient streaming media server. (Sometimes we end up with things like HTTP Live Streaming, a horrendously inefficient protocol designed for ease of use in programming environments, client- and server-side, that effectively only offer the equivalent of "fetch_url".)

Plus, IOCP models tend to heavily rely on callbacks and closures. And as demonstrated in the article, low-level languages suck at providing ergonomic first-class functions, especially if they lack GC. (It's a stretch to say that Rust even supports first-class functions.) If I were writing an asynchronous library in Rust, I'd do it the same way I'd do it in C--a low-level core that is non-blocking and stateful. For example, you repeatedly invoke something like "url_fetch_event", which returns a series of events (method, header, body chunk) or EAGAIN/EWOULDBLOCK. (It may not even pull from a socket directly, but rely on application to write source data into an internal buffer.) Then you can wrap that low-level core in progressively higher-level APIs, including alternative APIs suited to different async event models, as well as fully blocking interfaces. And if a high-level API isn't to some application developer's liking, they can create their own API around the low-level core API. This also permits easier cross-language integration. You can easily use such a low-level core API for bindings to Python, Lua, or even Go, including plugging into whatever event systems they offer, without losing functional utility.

It's the same principle with OS and systems language interfaces--you provide mechanisms that can be built upon. But so many Rust developers come from high-level application environments, including scripting language environments, where this composition discipline is less common and less relevant.

Re: Why asynchronous Rust doesn't work

#67

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…

FWIW I've done a fair bit of researching with io_uring. For file operations it's fast, bit over epoll the speedups are negligible. The creator is a good guy but they're having issues with the performance numbers being skewed due to various deficiencies in the benchmark code, such as skipping error checks in the past.

Also, io_uring can certainly be used via polling. Once the shared rings are set up, no syscalls are necessary afterward.

Re: Why asynchronous Rust doesn't work

#68

Earlier quoted context omitted.

That’s why it’s amazing, type safe efficient multi-threading without dynamic memory allocation with a nice syntax...it’s the holy grail of server programming.

It would be nice if it was explicitly a borrowing-related message. That's the kind of thing I can see happening in a future rustc actually.

Yeah, the good thing is that the devs are always happy to take feedback on improving the error messages (like explaining differences between Fn/FnMut/fn here)

Re: Why asynchronous Rust doesn't work

#69
post #23

> The thing I really want to try and get across here is that *Rust is not a language where first-class functions are ergonomic.* So... don’t use first-class functions so much? It’s a systems language, not a functional language for describing algorithms in CS whitepapers. Or use `move` (the article does mention this). There are easy paths in most programming languages, and harder paths. Rust is no exception. The fact…

> So... don’t use first-class functions so much? It’s a systems language, not a functional language for describing algorithms in CS whitepapers. Then maybe it was a mistake to adopt an async paradigm from functional languages that relies heavily on the idea that first-class functions are cheap and easy? (FWIW I think Rust was right to pick Scala-style async; it's really the only nice way of working with async that I'…

> Rust was right to pick Scala-style async

Huh, I actually find Rust and Scala to do async quite differently. The only thing in common to me is the monadic nature of Future itself. Otherwise I find there to be a big fundamental difference in how Scala's async is threadpool/execution-context based, while Rust's async is polling-based.

Then there are the syntactic differences around Rust having async/await and Scala... not.

Re: Why asynchronous Rust doesn't work

#70

Earlier quoted context omitted.

Correct me if I'm wrong, but isnt any sort of async support non integral to rust? For example in something like Javscript you can't impliment your own async. But in C, C++ or Rust you can do pretty much anything you want. So if in the future io-uring and friends become the standard can't that just be a library you could then use? Similar to how in C you don't need the standard library to do threads or async.

Yes, you can encode state machines manually, but it will be FAR less ergonomic than the async syntax. Rust has started with a library-based approach, but it was... not great. Async code was littered with and_then methods and it was really close to the infamous JS callback hell. The ergonomic improvements which async/await brings is essentially a raison d'être for incorporating this functionality into the language.

Can't Rust come up with a new syntax (that matches io_uring idea better) and deprecate the old one? Or simply replace the implementation keeping the old syntax if it's semantically the same?
Post reply on HN