Intentionally overflow on shift?

Doing x << y (where x: usize and y: u32) panics on debug builds if y is larger than the word size. I want the normal C behavior: this results in all 0s. However, it doesn't look like any of the usize methods support this. The best I can come up with is:

let res = {
    let (res, overflow) = x.overflowing_shl(y);
    if overflow { 0 } else { res }
};

This will probably be decently fast in practice, but it's pretty ugly (and probably not as fast as a single shift). Is there anything else I can do?

x.checked_shl(y).unwrap_or(0)

1 Like

The C behavior is undefined (section 6.5.7 of the C99 standard, still there in C11), in part because common processors can't decide what to do in this case (I believe x86 does the shift modulo-wordsize, where ARMv7 has the behavior you describe until the RHS crosses 255, and I think AArch64 changed this).

So you're going to be paying some runtime cost on most machines to get what you want. The code @birkenfeld suggests is clean, and if you want to avoid boilerplating it, put it in a new Trait and impl it for usize.

2 Likes

Huh fair enough. I've always been under the impression that it was well-defined (maybe in languages other than C?). Anyway, I'll do that; thanks!

Most languages I've seen that define it (such as .Net) take the shift amount modulo the size of the number, since that's what x86 does. (Which is fast, if not useful...)

1 Like

Hmm. I feel like I've definitely written code that assumes that the overflowing shift is valid (and results in 0), but it's possible that I just wrote code that used that assumption to justify its correctness in edge cases that, in practice, I never tested it with.

No worries, you're not the only one! I've found fairly serious bugs in shipping software because of this common misunderstanding.

Which is why I love Rust's default on this. :wink:

6 Likes

I know this thread is quite old, but instead of opening a new one I thought I just use this...
I'm also facing the issue:
attempt to shift left with overflow any use of this value will cause an error.

As my "shift assignment" is part of a const fn this approach: x.checked_shl(y).unwrap_or(0) is not working. Is there any option to solve this inside a const fn?

Very soon this should be possible on nightly, the following PR constifies both those functions:

https://github.com/rust-lang/rust/pull/66884

Until then you can use array indexing and some bit trickery to get the same sort of effect (playground)

const fn shl_or(val: u8, shift: usize, def: u8) -> u8 {
    [val << (shift & 7), def][((shift & !7) != 0) as usize]
}
2 Likes

great, thanks.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.