17 ms·
Asynchronous clean-up
- georgelyon 3y agoI’ve been following a similar issue in Swift (which is informed by some learnings from the Rust side of things). Here is a link to the latest language complexity resulting from this: https://forums.swift.org/t/sub-pitch-task-local-values-in-isolated-synchronous-deinit-and-async-deinit/70060/5 https://forums.swift.org/t/sub-pitch-task-local-values-in-is... There are a number of other proposals linked to this issue that can be referenced from that thread. I hope there is a next generation async model for future languages that is truly simple, because this all makes me think footguns are endemic to the current approach (which is broadly the same in Rust and Swift).
- 38 3y ago> I can write all these posts and tell you with a straight face there’s no tradeoff because appeasing the borrow checker is NBD. I never think about appeasing the borrow checker when I write Rust. Guy is in a bubble. He doesn't realize that 99% of rust programmers don't have the experience he does.
- conradludgate 3y agoWith my experience of moderating the Rust Community Discord Server, it really doesn't take long to get over the borrow checker. Most people are not writing code complicated enough to get stuck with the really complicated cases (unfortunately, yes, the borrow checker is not perfect) but it really is good enough for most people most of the time.
- the_duke 3y agoClicking with the borrow checker is a big hurdle for many, but once it clicks and you adjust your design patterns to be compatible with the language it really is a secondary concern. I very rarely run into borrow check errors, and if I do they are usually easily solved. From my impression that's the same for most semi-experienced Rust devs.
- TwentyPosts 3y agoThe borrow checker is really not that big of a deal. It is a hurdle, but the primary issue is just that it's novel and requires you to use different design patterns. If people get stuck here, it is because they don't understand that the dreaded borrow checker error is (generally) about large, systematic code design patterns, not about local changes. They try to appease the borrow checker by making small, local changes, which doesn't end up working, since borrow checker issues are about a fundamental issue in the ownership-design of your codebase. Once someone explains this to you, it's really not that hard.
- jandrewrogers 3y agoThis post is primarily about Rust but the issues raised generalize to async models in any programming language. The tradeoffs between approaches are severe enough that it is desirable, at least in a systems language, to preserve enough flexibility that the model generalizes to different contexts without loss of performance or robustness. Furthermore, async tends to erode modularity in code because async behavior, which is tightly coupled to internal design choices, becomes part of the functional contract. There are all kinds of interesting edge cases around things like bounding worst-case resource pseudo-leakage that you simply don’t need to think about in synchronous code. This is not to suggest that async models are a bad idea. They just have a higher level of intrinsic complexity and language support is immature. The benefit is qualitatively better scalability and performance, so there is a purpose behind using async models.
- withoutboats3 3y agoI liked the first sentence or two of your comment but I quickly stopped following. Any concurrency model has tightly coupled & specific semantics about scheduling and cancellation: that's what a concurrency model is meant to provide. The semantics of "synchronous code" are just treated as natural by programmers because it's what they've been taught their whole lives, but someone had to invent that too (Edsger Dijkstra & Tony Hoare, specifically). If all you mean to say is "the async model means whether a function synchronizes with a concurrent process becomes part of its contract", yes that's true, but the idea that this is "eroding modularity" and not "including essential facts about function behavior in its type signature" is an assertion of design principle, not a statement of fact.
- feverzsj 3y agoSeems java's virtual thread is the only sane async model out there.
- yogorenapan 3y agoI believe Golang uses a similar model but much more simple to use. Uncolored async is the way to go
- ribelo 3y agoGo's approach to async programming is indeed simpler, but its FFI and handling of stateful coroutines can introduce complexity and overhead when bridging with external code. Using CGO is slow because it requires synchronization of the coroutine state between libc and Go. Everyone avoids CGO whenever possible, so it is not a solution. Rust chose a different path that is not more complex, but the complexity lies elsewhere.
- yencabulator 3y agoJust an extra bit of clarity: the relative slowness of CGo calls is a conscious trade-off that enables Go threads to be much lighter weight. It's not an inherent aspect of the design, but a trade-off you pay for wanting more lightweight green threads. Choosing to not follow the age old C ABI internally means you have to bridge that gap if and when you come to it.
- Yoric 3y agoI'm of two minds on this point. Uncolored async is simpler language design and works well for _most_ cases but provides way fewer guarantees than colored async, with the potential to break badly in some cases. For instance, there are many system-level data structures that are not allowed to migrate from one thread to another (e.g. Linux mutexes or Rust's Rc) or sometimes from one core to another. If you adopt uncolored async (unless perhaps you're using a thread-per-core scheduler), you just can't manipulate these data structures. At all. Which means that you can't be a system programming language (for some definition of system programming). By the way, if you wish to test uncolored async in Rust, you can find an implementation here: https://github.com/Xudong-Huang/may https://github.com/Xudong-Huang/may .
- pdimitar 3y agoI am likely not even at 10% of the competence of the author but I can't help but notice the continuously mentioned struggle of "but what if we cancel the cancellation?" -- which to me seems to be the wrong question to ask. IMO it should not be possible. It should be made impossible by the compiler. The "everything is cancellable" rabbit hole is infinite and is not worth pursuing. Eventually the Rust core team will be inspecting CPU microcode and its bugs, or what's the endgame exactly? So let's not go there, I say. I know that Rust does not have a runtime and it does not have a spec but I think the questions posed in the article hint at the need for either -- or even both at the same time (i.e. "make a spec for the third party runtime implementors"). I am not saying "imitate Golang or Erlang". I am saying: "just pick a side already". On a more intuitive level: to me it feels like Rust tries to be everything for everyone, and I think many of us know that will never work. Choose. Commit. Double down. Make it work. Golang and Erlang did. Rust can do it as well.
- paholg 3y agoAsync Rust code is currently well-suited to both web servers and embedded. I, for one, would be very sad if they "just picked a side" that excluded one of these (very different) use cases.
- i_am_a_peasant 3y agoYeah, I literally do both, and it's sweet as hell.
- pdimitar 3y agoNowhere did I imply I want to take stuff from people. You could have read my comment charitably and without the unnecessary snark. Check my other sibling comment where I reply to the OP's author.
- paholg 3y agoI'm not sure where you read snark in my comment, but I can assure you it was unintentional.
- deleted 3y ago[deleted]
- u320 3y agoReading about async Rust is like reading about a large complicated infrastructure project that keeps getting delays and cost overruns. Rust will end up spending a significant part of its complexity budget on async alone. I wonder if it's worth it, for a problem that gets significantly easier if you are prepared to be just a bit more wasteful (allocations, memory, etc). Compare with Ocaml or Haskell for instance.
- i_am_a_peasant 3y agoIt's way ahead of C++ in terms of async. So much time is spent on async because it's important and most other languages have ignored it for a long time. Ever tried libuv in C? That was just a nightmare to work with, callbacks within callbacks within callbacks. Rust's approach to async is the best I've seen so far in any language, even high level languages like javascript that depend on async to even function.
- spinningslate 3y ago> Rust's approach to async is the best I've seen so far in any language I'm not a Rust programmer but have used async/await in both Python and C#. I've also written concurrent code in Erlang. I'd choose the Erlang approach over async/await every time. One concurrency primitive - the process - and an ergonomic, coherent set of supporting features (message passing, supervisors). No function colours and all the baggage that comes with that. Less well discussed but no less important: it puts concurrency decisions in the hands of the function caller, not the implementer. I understand Rust's focus on zero cost abstractions and, whilst I wouldn't pretend to understand the innards and consequences, get why green threads might not be compatible with that. OTOH that restriction doesn't hold for languages with a runtime like C# and Python. I'm increasingly convinced the compromises of async/await make it a poor language design for the concurrency problem when the language has a runtime. Perhaps we'll see some comparitive studies now Java has green threads. Erlang is different from C#/Python in many ways so straight comparison is hard. Java is much closer to C# so should be a much better basis for comparison.
- 3y ago
- hardwaresofton 3y agoIn the meantime I actually wrote a create that hacks around this problem: https://crates.io/crates/async-dropper https://crates.io/crates/async-dropper
- PoignardAzur 3y ago> On a UNIX system, the file is closed in the call to drop by calling close(2). What happens if that call returns an error? The standard library ignores it. This is partly because there’s not much you can do to respond to close(2) erroring, as a comment in the standard library elucidates Note that this is true for UNIX file descriptors, but not for C++ streams: an error when closing may indicate that the stream you closed had some buffered data it failed to flush (for any of the reasons that a failed write might come from). In that case, you sometimes do want to take a different codepath to avoid data loss; eg, if your database does "copy file X to new file Y, then remove X", if closing/flushing Y fails then you absolutely want to abort removing X. In the case of Rust code, the method you want is `File::sync_all`, which returns a Result for this exact purpose.
- asabil 3y ago> I don’t have an example of fully non-cooperative cancellation available off the top of my head. That would be Erlang.
- ggreg84 3y agoAsynchronous clean-up is a solved problem in C++ senders & receivers. I wonder what's so different about rust that they can't solve it in the same way.
- lordmauve 3y ago> I would be interested in examples of code that users believe require cancellation-specific async code, though. This happens all the time. For example, cancellation in the middle of sending a HTTP request. The connection is now unusable and must be closed. Without cancellation the connection returns to a state where it can be used and is re-added to a pool.