Why do overflow checks for `<<` not detect overflows?

An overflow, in the traditional sense, means that the result of an operation cannot be represented within the number of bits allocated to the result. E.g. let res: u8 = 255_u8 + 1_u8 overflows and panic in debug mode with runtime overflow checks.

However, this is not the case for the left shift operator in Rust. E.g. let res: u8 = 255_u8 << 4_u8 will run just fine and produce res == 240 in debug mode with overflow checks.

This behavior is intended and documented.

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. - ... - 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.

My question is why the language team decided that 255_u8 << 4_u8 should not be considered an overflow? What's the rationale behind this decision?

In my mind x << y should be the same as x * 2.pow(y), and that is indeed the case with wrapping multiplication (assuming y < bits_of(x)). E.g. 255_u8.wrapping_mul(1 << 4) == 240_u8. So the fact that the result of x << y is not considered when checking overflows seems like an oversight to me.


Motivation: My code had a bug, because I expected u64::checked_shl to return None when the result overflows, just like u64::checked_add and u64::checked_mul.