4 ms·
> just macro-decorate the main function, sprinkle some async/await around, maybe add a join or a mutex somewhere, and then pretty much forget all about event lo
by jstrong 6y ago
> just macro-decorate the main function, sprinkle some async/await around, maybe add a join or a mutex somewhere, and then pretty much forget all about event loops, messaging, threads and whatnot
that is ... not how I have experienced it. I work on building highly concurrent systems every day but async drives me insane. to me the fundamental issue is that although the code now reads linearly, it no longer executes linearly (or reasonably close to linearly), which is 1000x more confusing.
the other thing, when I'm using rust to build something high performance, part of the reason is it provides greater control. I just can't square that with macro-decorating my main function, and handing over the core control-flow to someone else's runtime.
- jgilias 6y agoThank you for the perspective! This actually explains it very well. So, in a nutshell, it's the difference between apparent and actual complexity, as well as a trust issue. With async/await the apparent complexity gets reduced at the cost of vastly increased actual complexity. E.g., now instead of everything being your code that you can look at and reason about, all your concurrent workloads disappear in this void that promises to do the right thing with them. If it works the way you intended, great. If it doesn't, the rabbit hole can now be really deep. And then, it's also a trust issue. Now you have to trust other people to have done a good job. Ok, yes, this makes sense.
- simias 6y agoYou answered jgilias better than I could, I feel exactly the same way. Async is deceptively simple in my opinion, because while it looks arguably even simpler than an explicit state machine, it makes your program flow nonlinear and I find that a lot harder to work with. With blocking code I can mentally step through the code follow causes and consequences easily, with async I feel like I'm watching a scifi movie involving time travel and parallel universes. And the loss of control is also an issue for me. I write code for memory-constrained environments, with blocking code and OS threads I can usually bound my memory consumption fairly easily. If I surrender the control to a scheduler runtime I feel like it becomes a lot harder, although here I'm willing to concede that it might have more to do with my lack of experience with Tokio than an objective issue.
- jstrong 6y agoagree 100%. it honestly kind of baffles me, "async" is like the programming community's white whale, and all of us get to come along for the chase. meanwhile, I long ago grew accustomed to the paradigm of an "event loop" in my programs. after a certain point it becomes very natural. on the subject of memory, recently there was an issue where the async dyn futures were blowing up stacks because a resolved future was > 2mb - what!? I mean, look at the signatures in the aturon article - we are going from this fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> ... to this ... fn read<T: AsMut<[u8]>>(self, buf: T) -> impl Future<Item = (Self, T, usize), Error = (Self, T, io::Error)> is that supposed to be easier? I recently tried writing a small program that would manually poll a future to get a feel for it - utter disaster. conflicting versions of tokio, compiles but crashes because something is called outside of the tokio runtime context, etc. all the examples have #[tokio::main]-decorated main - it's like, I'm not giving you my #$(&#@(&ing main function! the programs I write have tons of stuff going on! I can't just give some library my entire control flow! sorry for the rant! felt good to write it though.