6 ms·
Signed integer addition is only associative when overflow is defined to wrap around like unsigned arithmetic. This condition is matched here, because only debug
by GeertB 1mo ago
Signed integer addition is only associative when overflow is defined to wrap around like unsigned arithmetic. This condition is matched here, because only debug builds panic on overflow. However, it's a bit of a gray area that the article completely ignores.
- thrance 1mo agoUnlike C, Rust's signed arithmetic is fully specified to wrap around.
- aw1621107 1mo 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 1mo 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 1mo 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 1mo agoUnlike Rust and Cs before C23, C23's signed arithmetic is fully specified to wrap around. edit - nevermind, I'm wrong lol
- deleted 1mo ago[deleted]
- dzaima 1mo agoC23 specifies that signed integers must be two's complement, but still leaves signed arithmetic overflow as undefined behavior.
- pillmillipedes 1mo agoyeah, turns out you're right. god dammit. maybe one day
- wavemode 1mo 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 1mo 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.
- pdpi 1mo 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) } } }