Search code examples
rustlogical-operatorsbit-shiftinteger-overflow

How to Idiomatically Test for Overflow when Shifting Left (<<) in Rust?


For most operators that might overflow, Rust provides a checked version. For example, to test if an addition overflows one could use checked_add:

match 255u8.checked_add(1) {
    Some(_) => println!("no overflow"),
    None => println!("overflow!"),
}

This prints "overflow!". There is also a checked_shl, but according to the documentation it only checks if the shift is larger than or equal to the number of bits in self. That means that while this:

match 255u8.checked_shl(8) {
    Some(val) => println!("{}", val),
    None => println!("overflow!"),
}

is caught and prints "overflow!", This:

match 255u8.checked_shl(7) {
    Some(val) => println!("{}", val),
    None => println!("overflow!"),
}

simply prints 128, clearly not catching the overflow. What is the correct way to check for any kind of overflow when shifting left?


Solution

  • I'm not aware of any idiomatic way of doing this, but something like implementing your own trait would work: Playground

    The algorithm is basically to check if there are not fewer leading zeros in the number than the shift size

    trait LossCheckedShift {
        fn loss_checked_shl(self, rhs: u32) -> Option<Self> 
            where Self: std::marker::Sized;
    }
    
    impl LossCheckedShift for u8 {
        fn loss_checked_shl(self, rhs: u32) -> Option<Self> {
            (rhs <= self.leading_zeros()).then_some(self << rhs)
        }
    }
    
    fn main() {
        match 255u8.loss_checked_shl(7) {
            Some(val) => println!("{}", val),
            None => println!("overflow!"), // <--
        } 
        
        match 127u8.loss_checked_shl(1) {
            Some(val) => println!("{}", val), // <--
            None => println!("overflow!"),
        }
        match 127u8.loss_checked_shl(2) {
            Some(val) => println!("{}", val),
            None => println!("overflow!"), // <--
        }
    }