| I know you're just trashing C UB because that's fashionable, but if you really think about it there are two popular options, and you can't have both at the same time: a) UB enables valuable optimizations and is important to keep (or even add) when performance matters b) UB makes the language unusable/insecure to anyone but genius level experts and should be avoided Whenever someone (including famous/relevant people like Dennis Ritchie [0], DJ Bernstein [1], or Linus Torvalds [2]) tries to suggest cleaning up, removing, or simply not adding new cases of undefined behavior in C/C++, the optimization experts come running from the other room screaming about how important it is that "signed integer overflow must be undefined" [3] or else things will run a percent more slowly (signed overflow being just one example of UB). Also there are people who suggest adding new UB to Rust [4]. So really, either Rust is significantly slower than C because Rust doesn't have the UB you're criticizing, or C could be a cleaner language without compromising on speed and the compiler writers and standards committees are wrong. You choose, but both options are considered heresy. [0] https://www.lysator.liu.se/c/dmr-on-noalias.html [1] https://groups.google.com/g/boring-crypto/c/48qa1kWignU [2] https://lkml.org/lkml/2018/6/5/769 [3] https://youtu.be/yG1OZ69H_-o [4] https://www.ralfj.de/blog/2021/11/24/ub-necessary.html |
An example of this, is aliasing mutable references. Rust has been designed to assume that two mutable refs will never alias, if you attempt to do this it is instant UB. My understanding is that even creating the aliasing reference is UB, even if you don't use it.
Another example would be uninitialized values. In rust, the compiler assumes that values are always "valid" and initialized. Since you need to allocate memory before writing to it, you need some way to safely have uninit values, and this is what the MaybeUninit wrapper type is for. The wrapper allows you to safely have uninit values, and once you write to them you can tell the compiler that they are initialized, but if you tell the compiler before on accident, it is UB.
References also are guaranteed to point to initialized and valid data, and can never be null, though my understanding is that there is some uncertainty about the exact rules of this with regards to uninitialized values and the exact semantics may change in the future.
(There are also a lot more things that I don't know very much about!)
All of these things are assumed to never happen, and optimizations are performed based on that assumption.
The nice part about rust, is that it makes it impossible to represent invalid states using the type system!
For aliasing, you can only have a single live mutable reference at any time, attempting to create a second one while another is live is a compile time error!
For uninitialized values, you simply can't create uninitialized values at all in safe rust. My understanding is that the only way to create an uninitialized value is with the MaybeUninit type or by using raw pointers.
So rust still has heaps of UB, but it doesn't allow you to do it by default, so you still get some of the optimizations you'd expect.
I think there are some cases where rust is missing out on optimizations though, like with signed int overflow for example, and probably more that I don't know about!