3 ms·
I wouldn't call the coloring "a good thing". I would call it "an acceptable compromise". It would be better if you didn't have to label it and it all Just Worke
by jerf 5y ago
I wouldn't call the coloring "a good thing". I would call it "an acceptable compromise". It would be better if you didn't have to label it and it all Just Worked, just like it would be better if you didn't have to annotate lifetimes and it all Just Worked, but for the space Rust is operating in, it is an acceptable compromise. Rust sits high on the "force programmer to put more effort in, give programmer more out in return", on a pretty nice cost/benefit tradeoff place in that space. But the fact you get benefits commensurate with the costs doesn't mean it's not a cost at all, and it's probably not good to get into the habit of conflating costs and benefits. Clarity of view of both sides of the balance is very important for engineers.
- jayd16 5y agoWhat would be "just working" in your mind WRT promise, unwrap and thread context semantics? Sugar around thread yield, unwrap and context return? That seems like a lot of possible gotchas to simply be hidden and lurking under every benign looking synchronous method. Remember that the point of async/await is cooperative threading and not pre-emption. Hiding these details makes it much harder to reason about. If you don't know when your execution will be yielded, you're essentially being pre-empted, no?
- gpderetta 5y agoSure, you can be preempted by another thread (or even process) even within an async function, so why should it matter? The borrow checker should still prevent anything bad from happening. I'm not a rust programmer, but personally I think that stackless coroutines are probably a necessary evil in system languages as full stack switching might have too much overhead. But let's stop pretending that it is actually a good thing. Still I think that even with stackless coroutines await should be the default (the async keyword would be used if you explicitly want the promise), which, in addition to better ergonomics, would allow the async-ness of a function to be inferred, possibly as a function of its type parameters. This at the very least would avoid duplicating a lot of functionality.
- jayd16 5y ago>Sure, you can be preempted by another thread (or even process) even within an async function, so why should it matter? You misunderstand. Its not about whether your thread can be pre-empted, its that your logical execution can hold exclusive access to a thread. There are common design patterns that use exclusive threads as a means of synchronization.
- gpderetta 5y agoI understand. What I'm saying is that marking async points explicitly is neither necessary nor sufficient to guarantee exclusive use of the thread. The borrow checker or some other explicit critical section markers are a better soluiton.
- roblabla 5y ago> Sure, you can be preempted by another thread (or even process) even within an async function That really depends on the platform. Async rust is also being leveraged to build RTOS on embedded platform for instance. If async rust was preemptive by default, that would be impossible. It's not a matter of performance there, but of correctness.
- Matthias247 5y agoIt would actually be very hard to build a RTOS using async functions - and the current use of async/await on embedded Rust is indeed not about RTOS, it's just embedded code. The reason for that is that the point of a RTOS is providing guarantees: A certain task should be guaranteed to run within a certain time. To provide that guarantees, the RTOS needs to support preemption and prioritization. That allows it to schedule a certain task even if another task is running for too long. I'm not aware about any RTOS which makes use of cooperative multitasking.
- jlokier 5y ago> I think that stackless coroutines are probably a necessary evil in system languages as full stack switching might have too much overhead. I think it's worth noting that the big operating system kernels - perhaps the ultimate target of "system languages" - tend to use stackful coroutines rather than stackless. Stack switching tends to be similar speed overhead than stackless, despite surface appearances in how the code may appear. Stackless tends to do more memory allocation and freeing, though.
- jerf 5y agoGreen threads, like Go has, is one good answer. You don't run around annotating what is and is not async. It just works. Even full OS threads are an acceptable answer for the vast majority of applications. It is good and proper for embedded programmers and OS designers and such to have options as powerful as Rust, but that does not mean the average program written in Rust is embedded, an OS, or needs to be able to scale to hundreds of thousands of threads. (I actually think reaching straight for async/await is a mistake, personally. It's a tool for more extreme situations, not something you reach for when you're writing a program that's going to intermittently consume a fraction of a percent of CPU, or run a quick job where it may need maybe 10 threads if that and terminate. A lot of people badly overestimate the resources they need and jump straight for the extra-mega power tools for problems where they are literally a dozen orders of various magnitudes (CPU, RAM, etc.) away from needing them for their task.) (It is extra ironic to be terrified of threads when working in a language like Rust that has so thoroughly tamed them!) Yes, there are compromises, so you get the flip side of what I said above: Just because something has costs doesn't mean it doesn't have benefits. It is a benefit that you don't have to annotate everything yourself and the compiler and runtime just takes care of it. "Remember that the point of async/await is cooperative threading and not pre-emption." I disagree. Cooperative threading is a disaster. It doesn't scale. We tried it, very very hard, with an entire major consumer OS for decades. The point of async/await is precisely to get something like pre-emption into something that isn't using full, real threads (as its primary organization). That it functions like cooperative threading is one of the disadvantages that happens to be an acceptable compromise at most scales, not a goal.
- jayd16 5y ago>Cooperative threading is a disaster. It doesn't scale It doesn't need to scale because it solves a different problem. It find extreme success in UI and graphics programming where UI systems are dominated by a single UI thread for synchronization. GPU have been single threaded until recently. You can argue you do not like it but I can't see how you can argue against it's success. That said, I'm pretty interested to see a popular multithreaded/pre-emptive UI framework in the wild.
- dwaite 5y ago> It would be better if you didn't have to label it and it all Just Worked However, this is not possible. When you have concurrent code, whether you make it obvious or hide it, you have contention over any shared mutable state. async/await is meant to have the pieces with contention be cooperatively multitasked, and the yielding of control is explicit so that you can understand that the state going into the call may be different than the state once you come out - any number of unrelated things may have run in between when you awaited and when control returned. Not having coloring means that you have turned an explicit cooperative system to an implicit or even preemptive system. There are definite advantages and disadvantages to both, with the difficulty in tracking down and debugging concurrency issues in implicit/premptive systems being the reason that there has been so much research into other approaches such as async/await, actors, and structured concurrency.
- jerf 5y agoContention over mutable state is orthogonal to async/await in Rust. Proof is that they had the contention problem solved prior to async/await. Pure functional languages also solve the contention problem by simply destroying mutable state, without async labelling. A brute force solution, but a solution. async/await doesn't solve the problem of things possibly mutating things when you weren't running; you've turned over the thread of control, if your language permits mutation it can happen without you knowing. Rust solves that in other ways that have nothing to do with async/await, and its solution works with preemptive threading. Rust has been able to thread since long before async/await was introduced. It was introduced because for some of the use cases of Rust the overhead was unacceptable, not because prior to async you had no options at all to have that sort of functionality.
- gpderetta 5y agoCouldn't agree more with everything you have written.