Sure, you could pay the overhead of a managed language and have the runtime handle synchronization behind your back, but that isn't the path Rust went down.
In practice rust also tries to guide you towards patterns that reduces the risk of other bugs. For example, using Result, Option (and enums in general) reduces the risk that you forget to handle error paths. It is also quite hard to get memory leaks unintentionally in Rust, since by default you work with various smart pointers.
In my experience typical multi-threaded C++ has a lot of threading bugs that tend to cause hard to reproduce bugs (I work in safety critical hard realtime robotics / machine control, where we have a lot of communicating threads). When I code Rust I can trust the language to prevent those classes of bugs, letting me focus on the actual hard domain specific problems, rather than language minutiae. Yes, TSAN helps in C++, but that is dynamic analysis so it has to actually catch the potential problem happening while running.
So I think that the multithreading approach in Rust is really good and what we want, not a hindrance. It is absolutely the right thing to do.
Now, the async story is another matter and I have conflicting opinions on how that is done in rust. I can't think of a significantly better design that would work on microcontrollers / no-std though. I'm not interested in anything that requires allocations or a runtime.
Yes, that is true, but 1. that's not the same justification as for memory safety, and 2. that's a decision made by Rust, but it's not the only valid one (e.g. Zig has the "unordered" ordering, using LLVM's Java memory model support).
> In practice rust also tries to guide you towards patterns that reduces the risk of other bugs.
I understand that perspective, but let's say it's not hard to debate, and there's a clear difference between something that prevents a cause of a majority of vulnerabilities and something that prevents a small minority (while not preventing bigger causes).
> So I think that the multithreading approach in Rust is really good and what we want, not a hindrance. It is absolutely the right thing to do.
Well, it is absolutely what some people think is the right thing to do and also absolutely what others don't think is the right thing to do. It's an approach with both supporters and critics.
As soon as you have something larger than the hardware atomic size (or unaligned in memory) you risk torn reads/writes. So a pointer + a size for example on some architectures (some hardware like x86 has a double word atomic support, but on embedded you usually only get up to word size atomics).
All this means that you can't (in a systems level language) feasibly make everything atomic and ensure no UB.
All that said, do I wish Rust did more for other concurrency bugs? Yes, sure. Though there is some hope: I have seen Rust crates to enforce lock ordering at compile time (I think that originally came out of the Fuchsia OS kernel development). There is also scoped threads, rayon, actor frameworks etc. All different approaches to strucured concurrency, with the goal of reducing bugs. Different frameworks seem to suit different problems though. For what I do I rarely need mutexes, instead either rayon or message passing work well.
Java doesn't have this problem.
> As soon as you have something larger than the hardware atomic size (or unaligned in memory) you risk torn reads/writes. So a pointer + a size for example on some architectures (some hardware like x86 has a double word atomic support, but on embedded you usually only get up to word size atomics).
Well, the reading of a stale pointer alone could lead to UAF even without tearing.
> All this means that you can't (in a systems level language) feasibly make everything atomic and ensure no UB.
I didn't say no UB. I get that Rust decided to do something (no UB in the safe subset) and this is a consequence of that, but my point is that it's not obvious it's the best goal in the first place. Certain patterns are known to be the cause of a majority of vulnerabilities in C and C++, so it makes sense to sacrifice something to address them, but once you get to bugs that are no more common as vulnerabilities as many other bugs that Rust doesn't stop, saying we must stop these bugs and not others, even at the cost of requiring more programs to use the unsafe subset is one possible tradeoff but not a universally superior one.
Is that the case because it's easy to avoid concurrency issues, or because people are afraid of even trying to introduce concurrency into their program due to those issues? Because if it's the latter, then trying to support some amount of safe concurrency makes sense.
What Alan Kay called "the Wirth school of non-programming".
Either way, the problems that rust takes off the plate don't seem arbitrary for that reason. I don't particularly mind the problems it adds to the plate.
Calling a trade-off a slippery slope to a zero-set language is really disingenuous.
And I wasn't saying there's an actual slippery slope, only that the argument that if something is tricky it should be moved out of the safe subset is not a good one. The justification Rust uses to do this for memory safety is because memory safety was empirically found to be special, but that particular justification doesn't work for concurrency (nor is it the one used; again, in Rust this is a side-effect).
Data race freedom has been specified as a requirement for UB-freedom (i.e. safe code) since the C11/C++11 concurrency model, which predates the Rust borrow checker. You can make different choices on particular platforms, but they come with challenging tradeoffs: e.g. in Java, variables of type "long" are not data-race free on 32-bit platforms; writes to a long can silently tear.