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…
Why asynchronous Rust doesn't work
481–490 of 499 posts
Re: Why asynchronous Rust doesn't work
#482Earlier quoted context omitted.
The problems are (1) either you write two versions of almost every function or you can only support one use case (sync or async). If you could write generic functions which work in both cases it would be better. (2) it creates a lot of noise writing async/await. It's like if we had to always write x = call f() in the sync case.
If you write your functions as async, it's trivial to make a sync wrapper out of it.
Assuming it's possible, one way is to create a new event loop in a thread. This means you're creating and tearing down threads for a "trivial" wrapper. And, really, because threads are expensive, you'd probably want to let the caller provide an event loop.
Really, you wind up asking the caller to tell you what event loop to use. Let's compare real async vs. the sync wrapper function:
async func main() {
await middle()
}
async func middle() {
await libraryFunc()
}
# Using the sync wrapper without hiding the loop.
func main() {
loop = getLoop()
middle(loop)
}
func middle(loop) {
libraryFuncWrapper(loop)
}
You wind up "coloring" your functions through a parameter instead of by marking them "async".Perhaps your async implementation lets you use a globally defined event loop, but, really, globals are just backdoor parameters.
None of that means that such a wrapper is never going to be a good solution. Grabbing the global event loop probably works for many use cases. It's just that it doesn't avoid the coloring problem, it only hides it.
Re: Why asynchronous Rust doesn't work
#483I'll repeat this until my karma is zero: Erlang, Rust and Go have simple memory models and cannot do Joint (on the same memory) Parallelism efficiently. They are only fragmenting development. In my opinion there are only two programming languages worth mastering: C+ (C syntax compiled with cl/g++) on client and Java SE (8u181) on server. That said C++ (namespaces/string/stream) and JavaScript (HTML5) can be useful fo…
There is a reason why people are moving away from shared memory parallelism. It's not fun to deal with NUMA domains efficiently. For hardware engineers, it's not fun implementing cache coherency protocol efficiently. There is a reason why even Intel was experimenting with channels back in 2010 and you see more and more "network on chip", "cluster on chip" designs with non cache coherent memory. As we need more cores,…
Re: Why asynchronous Rust doesn't work
#484Earlier quoted context omitted.
> That is, even if Rust had a monad trait, that does not mean that Try and Future could both implement it. This is because, in Haskell, these things have the same signatures. In Rust, they do not have the same signature. For reference: This is nonsense; Try and Future are not the same thing in Haskell, there are plenty of functions you can only call with one or the other. The point of the monad abstraction is to abst…
Sorry, you're right! Ironically, I remembered the high level problem, but filled in the wrong concrete details. I also made the mistake of talking about Try/Future, and then switched to Iterator/Future. > is a subtype of Rust does not have subtyping. (except for lifetimes) The point is, we don't actually have an existence proof that this is possible, and a lot of evidence to the contrary. It may be possible, but it's…
This is frequently claimed but AFAICS it's no longer true post-impl trait. (I guess the counterargument is that impl trait is not a first-class type?). In any case, "Liskov-substitutable for" instead of "a subtype of" carries my point.
> The point is, we don't actually have an existence proof that this is possible, and a lot of evidence to the contrary. It may be possible, but it's not a simple "just do x."
I mean you could say the same for any feature not currently present in Rust - I appreciate that there's more to it but what I've followed of the discussions really didn't feel like there were clear blockers so much as a lack of interest. If there's genuinely a question mark about the possibility, would making an working implementation (that makes a lot of arbitrary choices about syntax, efficiency etc.) advance the conversation?
Re: Why asynchronous Rust doesn't work
#485Earlier quoted context omitted.
^this IMO, Rust already provides a decent amount of way to simplify and skip things. Reference counting, async, proc_macro, etc. In my experience, programming stuffs at a higher-level-language where things are heavily abstracted, like (cough) NodeJS, is easy and simple up to a certain point where I have to do a certain low-level things fast (e.g. file/byte patching) or do a system call which is not provided by the ru…
This is where Scala (and JVM based languages) would shine in theory, the JVM has a well defined memory model, provides great low-level tools, etc. (But JVM-based software is always very bulky to deploy, both in terms of memory and size, so this shining is rarely seen in practice.)
Anyway on abstraction, it's just hard, because everyone has different concept of what abstraction is. For some it's just combining couple of function calls into one, for some it is providing defaults, for some it is providing composable functions with controllable continuations.
Re: Why asynchronous Rust doesn't work
#486For a few months while I was between jobs, I decided to learn Rust. Because async code is supposed to be better than threaded code, I spent all my time trying to write async Rust. Big mistake! I never accomplished what I set out to accomplish. Most of my experience is in C#, where the difference between async and threaded code is mostly syntax sugar. (Until you get into the details.) (And async code makes writing UI…
I sympathize. As I said in another comment, the issue isn't really caused by async, IMO. It's caused by the complicated and leaky abstraction of traits. I'd be willing to bet that your difficulties were more around dealing with the interactions of async + traits than if you had just written a bunch of async functions and then ran them inside an async main (via tokio's tokio::main attribute). Just a guess.
Re: Why asynchronous Rust doesn't work
#487For a few months while I was between jobs, I decided to learn Rust. Because async code is supposed to be better than threaded code, I spent all my time trying to write async Rust. Big mistake! I never accomplished what I set out to accomplish. Most of my experience is in C#, where the difference between async and threaded code is mostly syntax sugar. (Until you get into the details.) (And async code makes writing UI…
You had chosen the really hard way to learn Rust. The most of problems for a beginner comes from lifetimes and borrowing. Async code by itself is harder topic when it comes to lifetimes and borrowing. When you use object in a concurrent manner, then you need to have two or more references to it, who would own object? When object has his owner, object lives. When object lost his owner, object dropped. Someone must own…
In the other languages where I used async, Javascript and C#, there is no penalty for using a variable across the async gap. The async keyword just means that something will block. (And plenty of other details, like your UI can paint itself.)
Re: Why asynchronous Rust doesn't work
#488Earlier quoted context omitted.
Sorry, you're right! Ironically, I remembered the high level problem, but filled in the wrong concrete details. I also made the mistake of talking about Try/Future, and then switched to Iterator/Future. > is a subtype of Rust does not have subtyping. (except for lifetimes) The point is, we don't actually have an existence proof that this is possible, and a lot of evidence to the contrary. It may be possible, but it's…
> Rust does not have subtyping. (except for lifetimes) This is frequently claimed but AFAICS it's no longer true post-impl trait. (I guess the counterargument is that impl trait is not a first-class type?). In any case, "Liskov-substitutable for" instead of "a subtype of" carries my point. > The point is, we don't actually have an existence proof that this is possible, and a lot of evidence to the contrary. It may be…
> would making an working implementation (that makes a lot of arbitrary choices about syntax, efficiency etc.) advance the conversation?
I don't know. It might, but it's also possible that the team has other objections I'm not aware of.
Re: Why asynchronous Rust doesn't work
#489Earlier quoted context omitted.
> Note that C++ offers async as a library and not as a programming language feature which Bjarne Stroustrup is strongly against. https://www.modernescpp.com/index.php/c-20-coroutines-the-fi...
coroutines themselves have nothing todo with async/await.
Re: Why asynchronous Rust doesn't work
#490Earlier quoted context omitted.
> Rust does not have subtyping. (except for lifetimes) This is frequently claimed but AFAICS it's no longer true post-impl trait. (I guess the counterargument is that impl trait is not a first-class type?). In any case, "Liskov-substitutable for" instead of "a subtype of" carries my point. > The point is, we don't actually have an existence proof that this is possible, and a lot of evidence to the contrary. It may be…
impl Trait is an existential type, not a subtype relationship. And it doesn't really mean "Liskov substitutable" either. After all, it names a single type, just un-named. > would making an working implementation (that makes a lot of arbitrary choices about syntax, efficiency etc.) advance the conversation? I don't know. It might, but it's also possible that the team has other objections I'm not aware of.
Well, "impl Iterator" is clearly not the same thing as "Vec", but "Vec" is Liskov-substitutable for it. AFAICS it meets every definition of a subtype unless you take the position that "impl Iterator" isn't a type at all, which begs the question of what "impl Iterator" is - certainly it's something more than syntax sugar for "Vec", and it looks like a type and largely quacks like a type AFAICS.