Shifting negative positions or a value greater than or equal to the width of the left operand
in shift left and shift right expressions [FLS-BIT-EXPRESSIONS]
are defined by this guideline to be out-of-range shifts.
The Rust FLS incorrectly describes this behavior as arithmetic overflow [FLS-ISSUE-632].
If the types of both operands are integer types,
the shift left expression lhs << rhs evaluates to the value of the left operand lhs whose bits are
shifted left by the number of positions specified by the right operand rhs.
Vacated bits are filled with zeros.
The expression lhs << rhs evaluates to \(\mathrm{lhs} \times 2^{\mathrm{rhs}}\),
cast to the type of the left operand.
If the value of the right operand is negative or greater than or equal to the width of the left operand,
then the operation results in an out-of-range shift.
If the types of both operands are integer types,
the shift right expression lhs >> rhs evaluates to the value of the left operand lhs
whose bits are shifted right by the number of positions specified by the right operand rhs.
If the type of the left operand is any signed integer type and is negative,
the vacated bits are filled with ones.
Otherwise, vacated bits are filled with zeros.
The expression lhs >> rhs evaluates to \(\mathrm{lhs} / 2^{\mathrm{rhs}}\),
cast to the type of the left operand.
If the value of the right operand is negative,
greater than or equal to the width of the left operand,
then the operation results in an out-of-range shift.
This rule applies to the following primitive types:
i8
i16
i32
i64
i128
u8
u16
u32
u64
u128
usize
isize
Any type can support << or >> if you implement the trait:
use core::ops::Shl;
#[allow(dead_code)]
struct MyType;
impl Shl<u32> for MyType {
type Output = MyType;
fn shl(self, _rhs: u32) -> Self::Output { MyType }
}
You may choose any type for the right operand (not just integers), because you control the implementation.
This rule is based on The CERT C Coding Standard Rule [CERT-C-INT34].
|
|
|
|
Avoid out-of-range shifts in shift left and shift right expressions.
Shifting by a negative value, or by a value greater than or equal to the width of the left operand
are non-sensical expressions which typically indicate a logic error has occurred. |
|
|
|
|
This noncompliant example shifts by a negative value (-1) and also by greater than or equal to the number of bits that exist in the left operand (40):.
should panic
fn main() {
let bits : u32 = 61;
let shifts = vec![-1, 4, 40];
for sh in shifts {
println!("{bits} << {sh} = {:?}", bits << sh);
}
}
|
|
|
|
|
This noncompliant example tests the value of sh to ensure the value of the right operand is negative or greater
than or equal to the width of the left operand.
fn main() {
let bits: u32 = 61;
let shifts = vec![-1, 0, 4, 40];
for sh in shifts {
if sh >= 0 && sh < 32 {
println!("{bits} << {sh} = {}", bits << sh);
}
}
}
|
|
|
|
|
The call to bits.wrapping_shl(sh) in this noncompliant example yields bits << mask(sh),
where mask removes any high-order bits of sh that would cause the shift to exceed the bitwidth of bits.
Note that this is not the same as a rotate-left.
The wrapping_shl has the same behavior as the << operator in release mode.
fn main() {
let bits : u32 = 61;
let shifts = vec![4, 40];
for sh in shifts {
println!("{bits} << {sh} = {:?}", bits.wrapping_shl(sh));
}
}
|
|
|
|
|
This noncompliant example uses bits.unbounded_shr(sh).
If sh is larger or equal to the width of bits,
the entire value is shifted out,
which yields 0 for a positive number,
and -1 for a negative number.
The use of this function is noncompliant because it does not detect out-of-range shifts.
fn main() {
let bits : u32 = 61;
let shifts = vec![4, 40];
for sh in shifts {
println!("{bits} << {sh} = {:?}", bits.unbounded_shr(sh));
}
}
|
|
|
|
|
The call to bits.overflowing_shl(sh) in this noncompliant shifts bits left by sh bits.
Returns a tuple of the shifted version of self along with a boolean indicating whether the shift value was larger than or equal to the number of bits.
If the shift value is too large, then value is masked (N-1) where N is the number of bits, and this value is used to perform the shift.
fn main() {
let bits: u32 = 61;
let shifts = vec![4, 40];
for sh in shifts {
let (result, overflowed) = bits.overflowing_shl(sh);
if overflowed {
println!("{bits} << {sh} shift too large");
} else {
println!("{bits} << {sh} = {result}");
}
}
}
|
|
|
|
|
This compliant example performs left shifts via the u32::checked_shl
function and right shifts via the u32::checked_shr function.
Both of these functions are defined in core.
<T>::checked_shl(M) returns a value of type Option<T>:
If M < 0, the output is None
If 0 <= M < N for T of size N bits, then the output is Some(T)
If N <= M, the output is None
Checked shift operations make programmer intent explicit and eliminates out-of-range shifts.
Shifting by:
negative values is impossible because checked_shl only accepts unsigned integers as shift lengths, and
greater than or equal to the number of bits that exist in the left operand returns a None value.
fn main() {
let bits : u32 = 61;
// let shifts = vec![-1, 4, 40];
// ^--- Compiler rejects negative shifts
let shifts = vec![4, 40];
for sh in shifts {
println!("{bits} << {sh} = {:?}", bits.checked_shl(sh));
}
}
|
|