The Rust Reference is underspecified about how overflow would behave in release mode

fn main() {
   let i = i32::MAX +1;
}

expr.operator.int-overflow only talks about how the behavior is under the debug mode

Integer operators will panic when they overflow when compiled in debug mode. The -C debug-assertions and -C overflow-checks compiler flags can be used to control this more directly. The following things are considered to be overflow:

  • When +, * or binary - create a value greater than the maximum value, or less than the minimum value that can be stored.
  • Applying unary - to the most negative value of any signed integer type, unless the operand is a literal expression (or a literal expression standing alone inside one or more grouped expressions).
  • Using / or %, where the left-hand argument is the smallest integer of a signed integer type and the right-hand argument is -1. These checks occur even when -C overflow-checks is disabled, for legacy reasons.
  • Using << or >> where the right-hand argument is greater than or equal to the number of bits in the type of the left-hand argument, or is negative.

It says nothing about how the behavior would be in the release mode. Where can I find the associated documents other than the book?

maybe this section is relevant, to quote:

When the programmer has enabled debug_assert! assertions (for example, by enabling a non-optimized build), implementations must insert dynamic checks that panic on overflow. Other kinds of builds may result in panics or silently wrapped values on overflow, at the implementation’s discretion.

In the case of implicitly-wrapped overflow, implementations must provide well-defined (even if still considered erroneous) results by using two’s complement overflow conventions.

although the reference is not a spec, the wording sounds to me very similar to C++ spec's "implementation defined behavior", with the constraint that the only allowed non-panicking behavior is silently wrapped value in two's complement representation.

2 Likes

For posterity, I had a huge brain fart dealing with modular arithmetic and erroneously stated that multiples of two exhibited truncation instead of wrapping when dealing with the pow method. Fortunately common sense returned, and I realized my silly mistake: obviously 2^n x m ≡ 0 (mod 2^n).

I will say though that << and >> arguably exhibit "surprising" behavior at least when compared to the wrapping behavior of arithmetic since the bits wrap back around on "overflow"; thus assert_eq!(0, 2u32.pow(32)) but assert_eq!(1, 1u32 << 32u8).

That's probably inspired by x86's shift operations where a 32-bit shift only reads the lower 5 bits of the shift amount, and a 64-bit shift only reads the lower 6 bits and similar for other sizes. Other bits are ignored.

2 Likes

I noticed that unbounded_shl/r got added recently, they may be of interest. You can also use the checked_ variants for explicitly always throwing versions.

1 Like

The behavior is documented in the Book, but yes, it should clearly be in the reference too.

Ah, nice little tidbit.

Yes, they're on my list of "unfixable mistakes in rust".

Wrapping should always be the answer you get is equivalent to computing it in infinite precision, then wrapping the result, but that's not at all what the wrapping shifts do. Similarly "checked" should be None if the wrapped result is different from what the infinite-precision one is, but that's not what the checked shifts do either.


Similarly I don't like methods like count_zeroes that depend on the width of the type rather than just the conceptual values. Given x:u32, if I x.checked_foo() is Some, I should get the same answer from (x as u64).checked_foo().

(Thus ilog2 is better than leading_zeroes, for example.)

1 Like

I'm confused on the last one, I can see where you're coming from in general for trailing_zeros and count_ones but count_zeros and leading_zeros in particular are meaningless without a size, and trailing_zeros should match that. For their likely purpose of counting a bit set, the mathematical meaning of the value isn't relevant either.

Then I wouldn't include them on u32 and friends. count_ones is pretty normal, but I can't actually think of another language with count_zeroes.

(Have it on bits types maybe, but not on numeric ones.)

1 Like