| I often hear that if a distinction exists, it should be represented in the type system; functions are either async or not, so we should track it at compile time. The thing most people don't understand about language design is that if you follow this line of thinking to the extreme, your complexity goes through the roof as all of the infectious constraints spread upward through your call graph, sometimes conflicting with other constraints. Rust is an interesting data point. It already has at least three infectious mechanisms, which spread constraints upwards (async vs synchronous, Sync vs !Sync ("data coloring"), &mut and borrowing in general). If you've ever been unable to propagate these infectious constraints upward because you couldn't change your signature (because it was a public stable API, or it was a trait override, or it was a `drop` method) then you've felt this complexity first hand. I believe this is the main reason that we see less (healthy) abstraction in Rust compared to other languages. Instead, I think a language should use these kinds of "infectiousnesses" very sparingly. In this order: * Find a solution that doesn't involve infectiousness. I think Loom did really well here. * Add an escape hatch (not one that stalls the entire async runtime, preferably). For example, interfaces are a good escape hatch for static types. * I very much like MrJohz's suggestion in [0]: invert a feature's infectiousness by changing the default color. If I had one PL-related wish, it would be for a mechanism that's non-infectious like Loom, but didn't involve its stack copying and didn't need a runtime. I have a few ideas along those lines, but we'll see if they pan out. [0] https://www.reddit.com/r/ProgrammingLanguages/comments/vofiy... |
"I want to..." "...block anywhere" "...use IO anywhere" "...mutate anything anywhere" '...have access to all state everywhere"
But, expanding on the articles point, we _do_ have to deal with these details. If I use IO anywhere in the call stack, then I make it unavailable for unit testing. If I block anywhere, then I potentially freeze some UI code. If I fire and forget async operations, I will have a harder time handling errors and knowing when an operation is finished. If I don't keep my mutable state minimal/isolated, I will encounter internal inconsistencies.
In my experience, keeping these concepts at the top, helps to keep your code testable and understandable.