6 ms·
Waiting asynchronously on multiple channels/signals. Heterogenous select is really nice.
by tel 3y ago
Waiting asynchronously on multiple channels/signals. Heterogenous select is really nice.
- mrkline 3y agoIt's great! But there's nothing about it that requires futures. It really annoys me that something like this isn't built-in: https://github.com/mrkline/channel-drain https://github.com/mrkline/channel-drain
- tel 3y agoThat works for channels, but being able to wait other asynchronous things is better. Timeouts for instance. We could imagine extending this to arbitrary poll-able things. And now we have futures, kind of.
- simias 3y agoIt really is, but I still favour "unsexy" manual poll/select code with a lot of if/elseing if it means not having to deal with async. I fully acknowledge that I'm an "old school" system dev who's coming from the C world and not the JS world, so I probably have a certain bias because of that, but I genuinely can't understand how anybody could look at the mess that's Rust's async and think that it was a good design for a language that already had the reputation of being very complicated to write. I tried to get it, I really did, but my god what a massive mess that is. And it contaminates everything it touches, too. I really love Rust and I do most of my coding in it these days, but every time I encounter async-heavy Rust code my jaw clenches and my vision blurs. At least my clunky select "runtime" code can be safely contained in a couple functions while the rest of the code remains blissfully unaware of the magic going on under the hood. Dear people coming from the JS world: give system threads and channels a try. I swear that a lot of the time it's vastly simpler and more elegant. There are very, very few practical problems where async is clearly superior (although plenty where it's arguably superior).
- lelanthran 3y ago> It really is, but I still favour "unsexy" manual poll/select code with a lot of if/elseing if it means not having to deal with async. > I fully acknowledge that I'm an "old school" system dev who's coming from the C world and not the JS world, so I probably have a certain bias because of that, but I genuinely can't understand how anybody could look at the mess that's Rust's async and think that it was a good design for a language that already had the reputation of being very complicated to write. I'm in the same "old school" system dev category as you, and I think that modern languages have gone off the deep end, and I complained about async specifically in a recent comment on HN: https://news.ycombinator.com/item?id=37342711 https://news.ycombinator.com/item?id=37342711 > At least my clunky select "runtime" code can be safely contained in a couple functions while the rest of the code remains blissfully unaware of the magic going on under the hood. And we could have had that for async as well, if languages were designed by the in-the-trenches industry developer, and not the "I think Haskell and Ocaml is great readability" academic crowd. With async in particular, the most common implementation is to color the functions by qualifying the specific function as async, which IMO is exactly the wrong way to do it. The correct way would be for the caller to mark a specific call as async. IOW, which of the following is clearer to the reader at the point where `foo` is called? Option 1: color the function async function foo () { // ... } ... let promise = foo (); let bar = await promise; Option 2: schedule any function function foo () { // ... } let sched_id = schedule foo (); ... let bar = await sched_id; Option 1 results in compilation errors for code in the call-stack that isn't async, results in needing two different functions (a wrapper for sync execution), and means that async only works for that specific function. Option 2 is more like how humans think - schedule this for later execution, when I'm done with my current job I'll wait for you if you haven't finished.
- jayd16 3y agoIsn't mixing async and sync code like this a recipe for deadlocks? What if your example code is holding onto a thread that foo() is waiting to use? Said another way, explain how you solved the problems of just synchronously waiting for async. If that just worked then we wouldn't need to proliferate the async/await through the stack.
- vacuity 3y ago> but I genuinely can't understand how anybody could look at the mess that's Rust's async and think that it was a good design for a language that already had the reputation of being very complicated to write. Rust adopted the stackless coroutine model for async tasks based on its constraints, such as having a minimal runtime by default, not requiring heap allocations left and right, and being amenable to aggressive optimizations such as inlining. The function coloring problem ("contamination") is an unfortunate consequence. The Rust devs are currently working on an effects system to fix this. Missing features such as standard async traits, async functions in traits, and executor-agnosticism are also valid complaints. Considering Rust's strict backwards compatibility guarantee, some of these will take a long time. I like to think of Rust's "async story" as a good analogue to Rust's "story" in general. The Rust devs work hard to deliver backwards compatible, efficient, performant features at the cost of programmer comfort (ballooning complexity, edge cases that don't compile, etc.) and compile time, mainly. Of course, they try to resolve the regressions too, but there's only so much that can be done after the fact. Those are just the tradeoffs the Rust language embodies, and at this point I don't expect anything more or less. I like Rust too, but there are many reasons others may not. The still-developing ecosystem is a prominent one.
- mplanchard 3y agoI read comments like this and feel like I’m living in some weird parallel universe. The vast majority of Rust I write day in and day out for my job is in an async context. It has some rough edges, but it’s not particularly painful and is often pleasant enough. Certainly better than promises in JS. I have also used system threads, channels, etc., and indeed there are some places where we communicate between long running async tasks with channels, which is nice, and some very simple CLI apps and stuff where we just use system threads rather than pulling in tokio and all that. Anyway, while I have some issues with async around futur composition and closures, I see people with the kind of super strong reaction here and just feel like I must not be seeing something. To me, it solves the job well, is comprehensible and relatively easy to work with, and remains performant at scale without too much fiddling.
- tel 3y agoHonestly, this is me too. The only thing I’d like to also see is OTP-like supervisors and Trio-like nurseries. They each have their use and they’re totally user land concerns.
- lenkite 3y agoActually, this "old school" approach is more readable even for folks who have never worked in the low-level C world. At-least everything is in front of your eyes and you can follow the logic. Unless code leveraging async is very well-structured, it requires too much brain-power to process and understand.
- tel 3y agoI think Rust’s async stuff is a little half baked now but I have hope that it will be improved as time goes on. In the mean time it is a little annoying to use, but I don’t mind designing against it by default. I feel less architecturally constrained if more syntactically constrained.
- Pauan 3y agoI'm curious what things you consider to be half-baked about Rust async. I've used Rust async extensively for years, and I consider it to be the cleanest and most well designed async system out of any language (and yes, I have used many languages besides Rust).
- tel 3y agoAsync traits come to mind immediately, generally needing more capability to existentially quantify Future types without penalty. Async function types are a mess to write out. More control over heap allocations in async/await futures (we currently have to Box/Pin more often than necessary). Async drop. Better cancellation. Async iteration.
- Pauan 3y ago> Async traits come to mind immediately, I agree that being able to use `async` inside of traits would be very useful, and hopefully we will get it soon. > generally needing more capability to existentially quantify Future types without penalty Could you clarify what you mean by that? Both `impl Future` and `dyn Future` exist, do they not work for your use case? > Async function types are a mess to write out. Are you talking about this? fn foo() -> impl Future<Output = u32> Or this? async fn foo() -> u32 > More control over heap allocations in async/await futures (we currently have to Box/Pin more often than necessary). I'm curious about your code that needs to extensively Box. In my experience Boxing is normally just done 1 time when spawning the Future. > Async drop. That would be useful, but I wouldn't call the lack of it "half-baked", since no other mainstream language has it either. It's just a nice-to-have. > Better cancellation. What do you mean by that? All Futures/Streams/etc. support cancellation out of the box, it's just automatic with all Futures/Streams. If you want really explicit control you can use something like `abortable`, which gives you an AbortHandle, and then you can call `handle.abort()` Rust has some of the best cancellation support out of any async language I've used. > Async iteration. Nicer syntax for Streams would be cool, but the combinators do a good job already, and StreamExt already has a similar API as Iterator.