3 ms·
The key is "that must be enforced at non-zero runtime cost." Safe Rust is indeed generally free of UB, but that requires bounds checks at a minimum. > I could
by aw1621107 3y ago
The key is "that must be enforced at non-zero runtime cost." Safe Rust is indeed generally free of UB, but that requires bounds checks at a minimum.
> I could imagine a world where we define annotations around “signed integer will never overflow” that you can add to hit paths but otherwise disallow optimizing around that UB
That's kind of what Rust does - overflow is not UB, but you can use unchecked_add/mul/div/sub to get UB-on-overflow if you explicitly want it.
- jeffbee 3y agoHow does Rust as a language avoid the "UB" traps that are fundamentally just compiler optimizations? For example in the case where you have an array of 8 elements, you access the element at index x, then (in program order) test whether x is less than 8. It seems than an optimizing compiler with a known-bits analysis could conclude that x is always less than 8, and remove that test. I don't know much about languages, so I am interested in how that trap is a consequence of C and avoided by Rust.
- aw1621107 3y agoThe Rust compiler/stdlib inserts bounds checks for accesses that cannot be proven to be safe, so in this example you'd get a panic on the out-of-bounds access itself instead of hitting the assert afterwards. If you use get_unchecked() to intentionally bypass those bounds checks, the assert is indeed removed, but that requires an unsafe block. Speaking more generally, Rust gates UB behavior behind unsafe blocks, so it's much harder to unintentionally hit UB than in C.
- Kranar 3y agoRust performs a runtime check to protect against out of bounds array accesses. In some cases those checks can be optimized out if the compiler can prove it's not needed.
- jeffbee 3y agoThis is my point precisely. Having proven to itself that 0 <= x < 8, the compiler could go on to remove any test for x < 8, right?
- heinrich5991 3y agoYes. It even does so in some cases.
- sgift 3y agoYes, and the compiler does that. That's one of the reasons that iterators are preferred over for-loops with manual access in Rust. While compilers can for very simple cases determine that you will always stay within the bounds (as in your example, where it can find at compile time that the length will always be 8 and no access outside happens) with iterators the compiler doesn't need to know the actual length and can always elide bound checks.
- GrumpySloth 3y agoPedantic note: compiler doesn’t elide bounds checks in code using iterators. They’re not there to begin with. Iterators in the standard library are implemented using unsafe code, which calls non-bounds-checking variants of functions getting elements from collections. There is nothing for the compiler left to do here regarding bounds-checking.
- sgift 3y agoPedantic, but interesting - didn't know that. Thanks!
- Animats 3y agoThe Rust compiler is smarter than that. Even for classic subscript-type loops, it can often elide the bounds checks. There was a discussion on this in HN a few months back, with machine code excerpts. An iterator and an explicit loop with subscripts both generated the same machine instructions. If the compiler can elide bounds checks in inner loops, that's usually enough. Bounds checks elsewhere rarely affect performance.
- gizmo686 3y agoRust performs runtime bounds checks on array access unless the compiler can prove that the index is in bounds. As such, removing the check is safe, because it is actually true that the program will never reach that line with an out of bounds array. This is in contrast to C, where the out of bounds access us merely undefined, so the compiler is allowed to have the program continue execution passed it.
- vlovich123 3y agoThere's lots of things beyond bounds checks that constitute UB and bounds checks are but 1 small item. Generally Rust avoids the runtime cost of that through a three prong strategy of idiomatic Rust amortizing the cost of a bounds check to 0 (e.g. efficient iterator implementations that don't need bounds checks), eliding the check altogether if the compiler can prove it's duplicate / unneeded for some reason, or providing unsafe Rust as an escape hatch when you absolutely need it. C++ already has something similar via `.at` vs `[]` although the shorter notation being the unsafer but faster option is debatable & likely the thing most people use by default. Explicit annotations are probably the better approach. It's also got a great ecosystem. I love the assume crate to do these annotations instead of writing unsafe code explicitly: assume!(unsafe: i < v.len()) Now you've explicitly written an assumption that will cause the compiler to elide the bounds check in release mode but still assert it in debug mode (vs just doing unsafe & using variants that bypass the bounds check).
- aw1621107 3y ago> There's lots of things beyond bounds checks that constitute UB and bounds checks are but 1 small item. Indeed, hence "at a minimum". The type system and choosing to avoid UB when defining some operations helps a lot as well, but those have no direct overhead so there's no runtime cost there. Bounds checks (and overflow checks, if those are ever added to release mode) are just the one thing that have a direct runtime cost when they aren't elided.
- mike_hock 3y agoThe key is that you can't get rid of it entirely, which is why Rust has `unsafe.`