Live data from Hacker News

Why asynchronous Rust doesn't work

theta.eu.org

321–330 of 499 posts

Re: Why asynchronous Rust doesn't work

#321

Earlier quoted context omitted.

"Ergonomic" is such a nebulous word as to be nearly useless honestly. I don't see how it is [unduly] inefficient or uncomfortable for a language at Rust's level to ensure that the programmer actually thinks through the execution of the code they are writing. If one doesn't want to think about such things - and there's absolutely nothing wrong with that! - there are plenty of higher level languages that can do that le…

The whole point of async is entirely ergonomics. Anything you can do in async you can do in continuation-passing style. I believe the point the author is making is that they added a feature for ergonomics' sake that ended up not being ergonomic; async-style programming usually coincides with first-class functions and closures, and those are painful in Rust.

> async-style programming usually coincides with first-class functions and closures, and those are painful in Rust.

Are they, though?

The example the author gives in the article tries to write a multithreaded program as if it is a singlethreaded one. Of course that is going to be painful, whether you're trying to use futures/promises or you're using callbacks. If you want to use multiple threads safely (i.e. at all) you need to use the right structures for the job - smart pointers, mutexes, etc. This is independent of language really, even dynamically typed languages like Ruby still have mutexes for when you want to use multiple threads (though they do take control away from the programmer in the form of things like global interpreter locks).

If you don't actually want to deal with the complexities of multithreading, then don't use it. For example there are a good number of languages/runtimes that only expose a single-threaded event loop to the programmer (even though they may use thread pools in the background). But I don't think "ergonomic" should necessarily mean "abstract away every single detail" or "things should Just Work even though one is straight-up not approaching the problem correctly".

Re: Why asynchronous Rust doesn't work

#323

Earlier quoted context omitted.

https://aturon.github.io/blog/2016/09/07/futures-design/ The completion based futures that Alex started with were also based on epoll. The performance issues it presented had nothing to do any sort of impedence mismatch between a completion based future and epoll, because there is no impedence issue. You are confused.

Thank you for the link! But immideately we can see the false equivalence: completion based API does not imply the callback-based approach. The article critigues the latter, but not the former. Earlier in this thread I've described how I see a completion-based model built on top of FSMs generated by compiler from async fns. In other words, the arguments presented in that article do not apply to this discussion. >The p…

>Earlier in this thread I've described how I see a completion-based model built on top of FSMs generated by compiler from async fns. In other words, the arguments presented in that article do not apply to this discussion.

I've been lurking your responses, but now I'm confused. If you are not using a callback based approach, then what are you using? Rust's FSM approach is predicated on polling; In other words if you aren't using callbacks, then how do you know that Future A has finished? If the answer is to use Rust's current systems, then that means the FSM is "polled" periodically, and then you still have "async Drop" problem as described in withoutboat's notorious article and furthermore, you haven't really changed Rust's design.

Edit: As I've seen you mention in other threads, you need a sound design for async Drop for this to work. I'm not sure this is possible in Rust 1.0 (as Drop isn't currently required to run in safe Rust). That said it's unfair to call async "rushed", when your proposed design wouldn't even work in Rust 1.0. I'd be hesitant to call the design of the entire language rushed just because it didn't include linear types.

Re: Why asynchronous Rust doesn't work

#324
post #255

Earlier quoted context omitted.

> Java 8 is the last free Oracle JDK, nothing added after 8 is really interesting enough to take the complexity hit that Java 9/10/11 etc. mean. The low latency GCs and the general performance improvements seem quite good though. > C# is an ok alternative but they went for value types Java is also getting value types (project Valhalla). > Go has no VM but uses GC, thay miss half of the requirements to make Joint Para…

Well, there was a real-time thing bought by Sun from Sweden but I'm still unclear as to how much of that has made it into the open-source part of the JDK. Time will tell! Joint Parallelism is a term that I invented, it means loosely defined: if two threads can read and write to the same memory at the same time without it causing too much loss... basically you need atomic concurrency which is only really efficient on…

The low latency GCs I'm referring to are ZGC and Shenandoah, both of which are in the JDK:

* https://openjdk.java.net/projects/zgc/

* https://openjdk.java.net/projects/shenandoah/

Thank you for the rest of the post, it's very insightful :)

Re: Why asynchronous Rust doesn't work

#325
post #214

Earlier quoted context omitted.

> The problems the author is describing might apply more to library authors I don't know much about Rust, but I found the examples in the article kind of simple . As far as I can see, the author is trying to spawn a new thread and share an object between the main thread and the new one. It's seems like an everyday paradigm. Perhaps the problems arise when passing complex objects instead of native types (i.e. u32).

> As far as I can see, the author is trying to spawn a new thread and share an object between the main thread and the new one. It's seems like an everyday paradigm. And it works very well in Rust as long as you understand the core concept of the language (ownership). Rust is special, it has this fundamental concept that you need to understand before going on, and that's add some inevitable learning curve. But once yo…

I have no doubt that it works well in Rust (with its core concepts and caveats).

I was really referring to the parent comment which said that the author of the article seems to be complaining about something that applies only to a specific area ("The problems the author is describing might apply more to library authors").

I said that to me, the examples in the article seemed more like an everyday paradigm (mundane, creating a thread and sharing an object).

Re: Why asynchronous Rust doesn't work

#326
post #57

I like to joke that the best way to encounter the ugliest parts of Rust is to implement an HTTP router. Hours and days of boxing and pinning, Futures transformations, no async fn in traits, closures not being real first class citizens, T: Send + Sync + 'static, etc. I call this The Dispatch Tax. Because any time you want more flexibility than the preferred static dispatch via generics can give you - oh, so you just w…

Definitely dynamic dispatch + async brings out a lot of pain points. But I only agree that the async part of that is unfortunate. Making dynamic dispatch have a little extra friction is a feature, not a bug, so to speak. Rust's raison d'être is "zero cost abstraction" and to be a systems language that should be viable in the same spaces as C++. Heap allocating needs to be explicit, just like in C and C++. But, I agre…

Oh yeah, I can totally relate to the Send-Sync-Unpin massaging, plus 'static bound for me. It's so weird that individually each of them kinda makes sense, but often you need to combine then and all of a sudden the understanding of combinations just does not.. combine. After a minute or two of trying to figure out what should actually go into that bound I give up, remove all of them and start adding them back one by one until the compiler is happy.

Re: Why asynchronous Rust doesn't work

#327
post #238
post #226

Earlier quoted context omitted.

Note that you don't need a closure in Rust to start a thread. You can just specify a function to be called (because functions implement FnOnce).

Thanks. My question emerged from the fact that all the Rust threads examples I see are in the closure form || {} .

Because the closure form is much more useful. You very rarely want to create a thread without first passing some state to it, and environment-passing with closures is a brilliantly ergonomic way to do this.

Re: Why asynchronous Rust doesn't work

#328

I'm not totally sure what the author is asking for, apart from refcounting and heap allocations that happen behind your back. In my experience async Rust is heavily characterised by tasks (Futures) which own their data. They have to - when you spawn it, you're offloading ownership of its state to an executor that will keep it alive for some period of time that is out of the spawning code's control. That means all dat…

I'm a giant Rust fanboy and have been since about 2016. So, for context, this was literally before Futures existed in Rust.

But, I only work on Rust code sporadically, so I definitely feel the pros and cons of it when I switch back and forth to/from Rust and other languages.

The problem, IMO, isn't about allocations or ownership.

In fact, I think that a lot of the complaints about async Rust aren't even about async Rust or Futures.

The article brings up the legitimate awkwardness of passing functions/closures around in Rust. But it's perfectly fair to say that idiomatic Rust is not a functional language, and passing functions around is just not the first tool to grab from your toolbelt.

I think the actual complaint is not about "async", but actually about traits. Traits are paradoxically one of Rust's best features and also a super leaky and incomplete abstraction.

Let's say you know a bit of Rust and you're kind of working through some problem. You write a couple of async functions with the fancy `async fn foo(&x: Bar) -> Foo` syntax. Now you want to abstract the implementation by wrapping those functions in a trait. So you try just copy+pasting the signature into the trait. The compiler complains that async trait methods aren't allowed. So now you try to desugar the signature into `fn foo(&x: Bar) -> impl Future` (did you forget Send or Unpin? How do you know if you need or want those bounds?). That doesn't work either because now you find out that `impl Trait` syntax isn't supported in traits. So now you might try an associated type, which is what you usually do for a trait with "generic" return values. That works okay, except that now your implementation has to wrap its return value in Box::pin, which is extra overhead that wasn't there when you just had the standalone functions with no abstraction. You could theoretically let the compiler bitch at you until it prints the true return value and copy+paste that into the trait implementation's associated type, but realistically, that's probably a mistake because you'd have to redo that every time you tweak the function for any reason.

IMO, most of the pain isn't really caused by async/await. It's actually caused by traits.

Re: Why asynchronous Rust doesn't work

#329

Funny how the Rust community used to regard JavaScript with contempt. Yet now when you consider how well JS was able to add support for asynchronous programming, it's pretty clear that JavaScript was actually a great programming language all along. JavaScript was designed to evolve. That's the difference between intelligence and wisdom. Unlike intelligence, wisdom takes into account the unknown.

The Rust community regards the JS community with contempt? Where does that opinion come from?

Rust was often touted as a superior alternative to Node.js writing servers. Perhaps this element of contempt is no longer there but it certainly was there before due to the competition. Similar competition existed between Node.js and Golang and there was similar contempt from Go community.

Re: Why asynchronous Rust doesn't work

#330

Earlier quoted context omitted.

> A bigger problem in my opinion is that Rust has chosen to follow the poll-based model This is an inaccurate simplification that, admittedly, their own literature has perpetuated. Rust uses informed polling: the resource can wake the scheduler at any time and tell it to poll. When this occurs it is virtually identical to completion-based async (sans some small implementation details). What informed polling brings to…

> the resource can wake the scheduler at any time and tell it to poll Isn't that called interrupting? The terminology seems a little off here, but perhaps that is only my perception.

No, it's not. Interrupting is when you call the scheduler at any time, even when it's doing some other work. When it's idle, it can not be interrupted.

Interruptions are something one really tries to restrict to the hardware - kernel layers. Because when people write interruption handlers, they almost always write them wrong.

Post reply on HN