Hacker News new | ask | show | jobs
by ragnese 1925 days ago
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 agree that async is really unergonomic once you go beyond the most trivial examples (some of which the article doesn't even cover).

Some of it is the choices made around the async/await design (The Futures, themselves, and the "async model" is fine, IMO).

But the async syntax falls REALLY flat when you want an async trait method (because of a combination-and-overlap of no-HKTs, no-GATs, no `impl Trait` syntax for trait methods) or an async destructor (which isn't a huge deal- I think you can just use future::executor::block_on() and/or use something like the defer-drop crate for expensive drops).

Then it's compounded by the fact that Rust has these "implicit" traits that are usually implemented automatically, like Send, Sync, Unpin. It's great until you write a bunch of code that compiles just fine in the module, but you go to plug it in to some other code and realize that you actually needed it to be Send and it's not. Crap- gotta go back and massage it until it's Send or Unpin or whatever.

Some of these things will improve (GATs are coming), but I think that Rust kind of did itself a disservice with stabilizing the async/await stuff, because now they'll never be able to break it and the Pin/Unpin FUD makes me nervous. I also think that Rust should have embraced HKTs/Monads even though it's a big can of worms and invites Rust devs to turn into Scala/Haskell weenies (said with love because I'm one of them).

2 comments

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.
Yep. Same. I've been doing Rust for years at this point (not full time, and with long gaps- granted), and it's exactly like you said: individually these things are simple, but then you're trying to figure out where you accidentally let a reference cross an await boundary that killed your automatic Unpin that you didn't realize you needed. Suddenly it feels like you don't understand it like you thought you did.

The static lifetime bound is annoying, too! I guess it crops up if you take a future and compose it with another one? Both future implementations have to be static types to guarantee they live long enough once passed into the new future.

Is there any chance of fixing Pin/Unpin via a later Rust edition?
I don't know. But the Rust team is VERY against introducing any breaking changes. So we're stuck with the semantics we have today.