For Rust vs C++, I'd say it'll be much easier to have a complete understanding of Rust. C++ is an immensely complex language, with a lot of feature interactions.
C# is actually fairly complex. I'm not sure if it's quite at the same level as Rust, but I wouldn't say it's that far behind in difficulty for complete understanding.
So in Rust an unsafe block and an unsafe function mean two different things. An unsafe block allows you to do things that are unsafe, such as dereference raw pointers, access union fields, calling unsafe functions, etc.
Unsafe functions mark that the caller is responsible for upholding the invariants necessary to avoid UB. In the 2021 and earlier editions, they also implicitly created an unsafe block in the body, but don't in 2024.
Or, in a more pithy tone: an unsafe block is the "hold my beer" block, while an unsafe function is a "put down your beer" function.
A segfault is not the program performing a runtime check and doing a controlled shutdown. A segfault is the OS detecting the program doing something it's not allowed to and killing it.
Only if that memory page is unmapped, and only if the optimizer doesn't detect that it's a null pointer and start deleting verification code because derefing null is UB, and UB is assumed to never happen.
> Take Herb Sutter for example, who argues that "memory safety" as defined in this article is an extreme goal and we should instead focus on a more achievable 95% safety instead to spend the remaining effort on other types of safety.
One thing I've noticed when people make these arguments is that they tend to ignore the fact that most (all?) of these other safeties they're talking about depend on being able to reason about the behaviour of the program. But when you violate memory safety a common outcome is undefined behaviour, which has unpredictable effects on program behaviour.
These other safeties have a hard dependency on memory safety. If you don't have memory safety, you cannot guarantee these other safeties because you can no longer reason about the behaviour of the program.
For C/C++, memory safety is a retrofit to a language never designed for it.
Many people, including me, have tried to improve the safety of C/C++ without breaking existing code. It's a painful thing to attempt. It doesn't seem to be possible to do it perfectly. Sutter is taking yet another crack at that problem, hoping to save C/C++ from becoming obsolete, or at least disfavored. Read his own words to see where he's coming from and where he is trying to go.
Any new language should be memory safe. Most of them since Java have been.
The trouble with thinking about this in terms of "95% safe" is that attackers are not random. They can aim at the 5%.
The most popular ones have not been necessarily. Notably Go, Zig, and Swift are not fully memory safe (I’ve heard this may have changed recently for swift).
Go's memory safety blows up under concurrency. Non-trivial data races are Undefined Behaviour in Go, violating all safety considertions including memory safety.
I would not expect that it makes sense to provide this as the default for Go's hash table type, my understanding is that modern Go has at least a best effort "fail immediately" detector for this particular case, so when you've screwed this up your code will exit, reporting the bug, in production and I guess you can curse "stupid" Go for not allowing you to write nonsense if you like, or you could use the right tool for the job.
There are also limits to what the borrow checker is capable of verifying. There will always be programs which are valid under the rules the borrow checker is enforcing, but the borrow checker rejects.
It's kinda annoying when you run into those. I think I've also ran into a situation where the borrow checker itself wasn't the issue, but rather the way references were created in a pattern match causing the borrow checker to reject the program. That was also annoying.
Polonius hopefully arrives next year and reduces the burden here further. Partial field borrows would be huge so that something like obj.set_bar(obj.foo()) would work.
Given the troubles with shipping Polonius, I imagine that there isn't much more room for improvements in "pure borrow checking" after Polonius, though more precise ways to borrow should improve ergonomics a lot more. You mentioned borrowing just the field; I think self-referential borrows are another.
Zig only does bounds checking by default in Debug and ReleaseSafe builds. If you build with ReleaseFast or ReleaseSmall it will happily do an out of bounds read: https://godbolt.org/z/733PxPEPY
That's a matter of how policy is set. You can set it to on or off for a particular function, too. The point is that language offers sound spatial safety just as much as Rust does (and both allow you to turn it on or off in particular pieces of code).
C# is actually fairly complex. I'm not sure if it's quite at the same level as Rust, but I wouldn't say it's that far behind in difficulty for complete understanding.