Why is it allowed to use signed type as second argument in shift?

Why is it allowed to use signed type as second argument in shift ?

fn main() {
    my_shift(5, -128);
}
fn my_shift(value:i8, shift:i8 ) -> i8
{
    value << shift
}

Why will this code compile and crash during execution?

Shouldn't shift be limited to unsigned since if we pass a negative number we'll still get panic with "attempt to shift left with overflow"?

I would guess this was a mistake. It doesn't even do what you'd expect when debug assertions are off. It does value << shift as u8 instead of value >> -shift. This probably reflects the behavior of the hardware, but isn't very useful to humans. The shift methods just take u32.

Maybe an accidental consequence of the default Rhs in Shl<Rhs = Self>?

Doesn't look like it. 1.0 didn't have a default (I don't think defaults were in the language yet) and already had all the impls core::ops::Shl - Rust

Rust 1.0 had default parameters. But adding a default for Shl wasn't added until 1.27.

Used to be $i: Shl<$i>, then it became $i: Shl<u32>, then it became $i: Shl<$j>.[1] Doesn't look like there was much discussion.


  1. After adjusting to match today's type names and trait shape. ↩ī¸Ž

1 Like

Because with how inference works, there's two options:

  • You can shift by u32 only. Nothing else.
  • You can shift by every unsigned and signed type.

If you try to make just unsigned types work, then x << 1 stops working because the 1 ends up defaulting to i32, then fails to compile.

6 Likes

It would be reasonable for shifts to only work with u32 because all the other bit-count-oriented APIs only work with u32:
BITS, count_ones, ilog2, leading_ones, pow, etc.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.