6 ms·
Unlike C, Rust's signed arithmetic is fully specified to wrap around.
by thrance 2mo ago
Unlike C, Rust's signed arithmetic is fully specified to wrap around.
- aw1621107 2mo ago> Rust's signed arithmetic is fully specified to wrap around. Well, kind of. It's currently documented to wrap in release mode by default, but it's just that - a default. You're free to enable overflow checks in release mode (or disable them in debug if you really like oddball configurations), and either way overflow is considered a logic error that devs shouldn't rely on (and basically can't rely on when not in control of the end binary since it's the end user who controls overflow checks). The Rust devs are theoretically open to making signed overflow panic by default, but consider such a change unlikely unless "something materially changes" [0]. [0]: https://github.com/rust-lang/rust/issues/47739#issuecomment-3165061176 https://github.com/rust-lang/rust/issues/47739#issuecomment-...
- raverbashing 2mo agoThanks for clarification, it seems Rust devs (as opposite to C devs) like good defaults and don't like making code accidentally cut yourself just because you looked at it wrong
- tialaramex 2mo agoNo. If you want wrapping, ask for it with Wrapping<T> or the specific Wrapping types, or the wrapping arithmetic APIs It's true that since it's safe and faster, release builds default to wrapping rather than panic, but it's still wrong if you overflow any of Rust's default integer types, it's just that in a safe language it won't be Undefined Behaviour. "I can't be bothered to do it correctly" speaks to the quality of the rest of the product, it's a Brown M&M [read about the Van Halen test if you don't know what a Brown M&M means]
- pillmillipedes 2mo agoUnlike Rust and Cs before C23, C23's signed arithmetic is fully specified to wrap around. edit - nevermind, I'm wrong lol
- dzaima 2mo agoC23 specifies that signed integers must be two's complement, but still leaves signed arithmetic overflow as undefined behavior.
- pillmillipedes 2mo agoyeah, turns out you're right. god dammit. maybe one day
- wavemode 2mo agoProbably never. This has been debated to death by both C and C++ standards committees. The consensus is that, since signed overflow is almost always a sign of a bug in the program, keeping it undefined enables compilers to optimize by assuming it never happens, and also allows sanitizers to continue to flag it to developers so that they fix their bugs (though it could be argued that, a sanitizer doesn't really have to strictly adhere to the standard, the committee apparently didn't feel that way).
- aw1621107 2mo ago> and also allows sanitizers to continue to flag it to developers so that they fix their bugs I've never really found this argument particularly convincing; as you say, sanitizers don't have to strictly adhere to the standard, and they do in fact take advantage of this flexibility to check behaviors that "are not undefined behavior, but are often unintentional" (e.g., -fsanitize=unsigned-integer-overflow). Makes me wonder whether "sanitizers can't flag defined behavior" is meant to be shorthand for some more nuanced position ("the false positive rate for signed overflow sanitizer checks would be too high", maybe?) or something else.
- dzaima 2mo ago> -fsanitize=unsigned-integer-overflow Of course, -fsanitize=unsigned-integer-overflow isn't enabled by default, and few people use it (github code search gives 6K results for that, compared to 175K for "-fsanitize=undefined"; which to be fair is a lot higher than I expected, but still not a lot). > Makes me wonder whether "sanitizers can't flag defined behavior" is meant to be shorthand for some more nuanced position And signed overflow checking would have to be off-by-default too, if people were allowed to start relying on it. It'd be less "false positive rate too high", more "it disallows you to use a genuine language feature that is actually useful", defeating the point of defining signed overflow in the first place. (imo defining signed overflow specifically for reducing attack surface from exploitable UB is a mostly-separate discussion, which should not affect core language semantics, and certainly not what users would be suggested to do)
- pdpi 2mo agoThis deserves elaborating on, because it's pretty cool. Rust has two behaviours around overflow. In debug builds, it panics, in release builds it wraps. IMO wrapping is a reasonable-enough behaviour to avoid UB in release builds, and panicking in debug is definitely the correct behaviour, because you're only avoiding UB by defaulting to something, but that's not nearly enough. In most applications where overflow is a risk you should make sure to choose what behaviour you consider correct. Thankfully Rust has a pretty robust story around this: fn add_behaviour() { let small: i32 = 123; let big: i32 = i32::MAX; assert_eq!(small.wrapping_add(big), i32::MIN + 122); assert_eq!(small.overflowing_add(big), (i32::MIN + 122, true)); assert_eq!(small.overflowing_add(small), (246, false)); assert_eq!(small.saturating_add(big), i32::MAX); assert_panics!(a.strict_add(b)); // (nb: Not a real assertion) } And you could easily implement the default behaviour yourself with conditional compilation: impl Add for u32 { type Output = u32; fn add(self, rhs: u32) -> u32 { #[cfg(debug_assertions)] { self.strict_add(rhs) } #[cfg(not(debug_assertions))] { self.wrapping_add(rhs) } } }