Expressions

Guideline: Ensure that integer operations do not result in arithmetic overflow gui_dCquvqE1csI3
status: draft
tags: security, performance, numerics
category: required
decidability: decidable
scope: system
release: 1.0 - latest

Eliminate arithmetic overflow of both signed and unsigned integer types. Any wraparound behavior must be explicitly specified to ensure the same behavior in both debug and release modes.

This rule applies to the following primitive types:

  • i8

  • i16

  • i32

  • i64

  • i128

  • u8

  • u16

  • u32

  • u64

  • u128

  • usize

  • isize

Rationale: rat_LvrS1jTCXEOk
status: draft
parent needs: gui_dCquvqE1csI3

Eliminate arithmetic overflow to avoid runtime panics and unexpected wraparound behavior. Arithmetic overflow will panic in debug mode, but wraparound in release mode, resulting in inconsistent behavior. Use explicit wrapping or saturating semantics where these behaviors are intentional. Range checking can be used to eliminate the possibility of arithmetic overflow.

Non-Compliant Example: non_compl_ex_cCh2RQUXeH0N
status: draft
parent needs: gui_dCquvqE1csI3

This noncompliant code example can result in arithmetic overflow during the addition of the signed operands si_a and si_b:

fn add(si_a: i32, si_b: i32) {
  let _sum: i32 = si_a + si_b;
  // ...
}
Compliant Example: compl_ex_BgUHiRB4kc4b_1
status: draft
parent needs: gui_dCquvqE1csI3

This compliant solution ensures that the addition operation cannot result in arithmetic overflow, based on the maximum range of a signed 32-bit integer. Functions such as overflowing_add, overflowing_sub, and overflowing_mul can also be used to detect overflow. Code that invoked these functions would typically further restrict the range of possible values, based on the anticipated range of the inputs.

use std::i32::{MAX as INT_MAX, MIN as INT_MIN};

fn add(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> {
    if (si_b > 0 && si_a > INT_MAX - si_b)
        || (si_b < 0 && si_a < INT_MIN - si_b)
    {
        Err(ArithmeticError::Overflow)
    } else {
        Ok(si_a + si_b)
    }
}

fn sub(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> {
    if (si_b < 0 && si_a > INT_MAX + si_b)
        || (si_b > 0 && si_a < INT_MIN + si_b)
    {
        Err(ArithmeticError::Overflow)
    } else {
        Ok(si_a - si_b)
    }
}

fn mul(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> {
    if si_a == 0 || si_b == 0 {
        return Ok(0);
    }

    // Detect overflow before performing multiplication
    if (si_a == -1 && si_b == INT_MIN) || (si_b == -1 && si_a == INT_MIN) {
        Err(ArithmeticError::Overflow)
    } else if (si_a > 0 && (si_b > INT_MAX / si_a || si_b < INT_MIN / si_a))
        || (si_a < 0 && (si_b > INT_MIN / si_a || si_b < INT_MAX / si_a))
    {
        Err(ArithmeticError::Overflow)
    } else {
        Ok(si_a * si_b)
    }
}
Compliant Example: compl_ex_BgUHiRB4kc4c
status: draft
parent needs: gui_dCquvqE1csI3

This compliant example uses safe checked addition instead of manual bounds checks. Checked functions can reduce readability when complex arithmetic expressions are needed.

fn add(si_a: i32, si_b: i32) -> Result<i32, ArithmeticError> {
    si_a.checked_add(si_b).ok_or(ArithmeticError::Overflow)
}

fn sub(a: i32, b: i32) -> Result<i32, ArithmeticError> {
    a.checked_sub(b).ok_or(ArithmeticError::Overflow)
}

fn mul(a: i32, b: i32) -> Result<i32, ArithmeticError> {
    a.checked_mul(b).ok_or(ArithmeticError::Overflow)
}
Compliant Example: compl_ex_BgUHiRB4kc4b
status: draft
parent needs: gui_dCquvqE1csI3

Wrapping behavior must be explicitly requested. This compliant example uses wrapping functions.

fn add(a: i32, b: i32) -> i32 {
    a.wrapping_add(b)
}

fn sub(a: i32, b: i32) -> i32 {
    a.wrapping_sub(b)
}

fn mul(a: i32, b: i32) -> i32 {
    a.wrapping_mul(b)
}
Compliant Example: compl_ex_BhUHiRB4kc4b
status: draft
parent needs: gui_dCquvqE1csI3

Wrapping behavior call also be achieved using the Wrapping<T> type as in this compliant solution. The Wrapping<T> type is a struct found in the std::num module that explicitly enables two’s complement wrapping arithmetic for the inner type T (which must be an integer or usize/isize). The Wrapping<T> type provides a consistent way to force wrapping behavior in all build modes, which is useful in specific scenarios like implementing cryptography or hash functions where wrapping arithmetic is the intended behavior.

use std::num::Wrapping;

fn add(si_a: Wrapping<i32>, si_b: Wrapping<i32>) -> Wrapping<i32> {
    si_a + si_b
}

fn sub(si_a: Wrapping<i32>, si_b: Wrapping<i32>) -> Wrapping<i32> {
    si_a - si_b
}

fn mul(si_a: Wrapping<i32>, si_b: Wrapping<i32>) -> Wrapping<i32> {
    si_a * si_b
}

fn main() {
    let si_a = Wrapping(i32::MAX);
    let si_b = Wrapping(i32::MAX);
    println!("{} + {} = {}", si_a, si_b, add(si_a, si_b))
}
Compliant Example: compl_ex_BgUHiSB4kc4b
status: draft
parent needs: gui_dCquvqE1csI3

Saturation semantics means that instead of wrapping around or resulting in an error, any result that falls outside the valid range of the integer type is clamped:

  • To the maximum value, if the result were to be greater than the maximum value, or

  • To the minimum value, if the result were to be smaller than the minimum,

Saturation semantics always conform to this rule because they ensure that integer operations do not result in arithmetic overflow. This compliant solution shows how to use saturating functions to provide saturation semantics for some basic arithmetic operations.

fn add(a: i32, b: i32) -> i32 {
    a.saturating_add(b)
}

fn sub(a: i32, b: i32) -> i32 {
    a.saturating_sub(b)
}

fn mul(a: i32, b: i32) -> i32 {
    a.saturating_mul(b)
}
Compliant Example: compl_ex_BgUHiSB4kd4b
status: draft
parent needs: gui_dCquvqE1csI3

Saturating<T> is a wrapper type in Rust’s core library (core::num::Saturating<T>) that makes arithmetic operations on the wrapped value perform saturating arithmetic instead of wrapping, panicking, or overflowing. Saturating<T> is useful when you have a section of code or a data type where all arithmetic must be saturating. This compliant solution uses the Saturating<T> type to define several functions that perform basic integer operations using saturation semantics.

use std::num::Saturating;

fn add(si_a: Saturating<i32>, si_b: Saturating<i32>) -> Saturating<i32> {
    si_a + si_b
}

fn sub(si_a: Saturating<i32>, si_b: Saturating<i32>) -> Saturating<i32> {
    si_a - si_b
}

fn mul(si_a: Saturating<i32>, si_b: Saturating<i32>) -> Saturating<i32> {
    si_a * si_b
}

fn main() {
    let si_a = Saturating(i32::MAX);
    let si_b = Saturating(i32::MAX);
    println!("{} + {} = {}", si_a, si_b, add(si_a, si_b))
}
Non-Compliant Example: non_compl_ex_cCh2RQUXeH0O
status: draft
parent needs: gui_dCquvqE1csI3

This noncompliant code example example prevents divide-by-zero errors, but does not prevent arithmetic overflow.

fn div(s_a: i64, s_b: i64) -> Result<i64, DivError> {
    if s_b == 0 {
        Err(DivError::DivisionByZero)
    } else {
        Ok(s_a / s_b)
    }
}
Compliant Example: compl_ex_BgUHiRB4kc4d
status: draft
parent needs: gui_dCquvqE1csI3

This compliant solution eliminates the possibility of both divide-by-zero errors and arithmetic overflow:

fn div(s_a: i64, s_b: i64) -> Result<i64, DivError> {
    if s_b == 0 {
        Err(DivError::DivisionByZero)
    } else if s_a == i64::MIN && s_b == -1 {
        Err(DivError::Overflow)
    } else {
        Ok(s_a / s_b)
    }
}
Guideline: Avoid as underscore pointer casts gui_HDnAZ7EZ4z6G
status: draft
tags: readability, reduce-human-error
category: required
decidability: decidable
scope: module
release: <TODO>

Code must not rely on Rust’s type inference when doing explicit pointer casts via var as Type or core::mem::transmute. Instead, explicitly specify the complete target type in the as expression or core::mem::transmute call expression.

Rationale: rat_h8LdJQ1MNKu9
status: draft
parent needs: gui_HDnAZ7EZ4z6G

var as Type casts and core::mem::transmutes between raw pointer types are generally valid and unchecked by the compiler as long the target pointer type is a thin pointer. Not specifying the concrete target pointer type allows the compiler to infer it from the surroundings context which may result in the cast accidentally changing due to surrounding type changes resulting in semantically invalid pointer casts.

Raw pointers have a variety of invariants to manually keep track of. Specifying the concrete types in these scenarios allows the compiler to catch some of these potential issues for the user.

Non-Compliant Example: non_compl_ex_V37Pl103aUW4
status: draft
parent needs: gui_HDnAZ7EZ4z6G

The following code leaves it up to type inference to figure out the concrete types of the raw pointer casts, allowing changes to with_base’s function signature to affect the types the function body of non_compliant_example without incurring a compiler error.

#[repr(C)]
struct Base {
   position: (u32, u32)
}

#[repr(C)]
struct Extended {
   base: Base,
   scale: f32
}

fn non_compliant_example(extended: &Extended) {
   let extended = extended as *const _;
   with_base(unsafe { &*(extended as *const _) })
}

fn with_base(_: &Base) {}
Compliant Example: compl_ex_W08ckDrkOhkt
status: draft
parent needs: gui_HDnAZ7EZ4z6G

We specify the concrete target types for our pointer casts resulting in a compilation error if the function signature of with_base is changed.

#[repr(C)]
struct Base {
   position: (u32, u32)
}

#[repr(C)]
struct Extended {
   base: Base,
   scale: f32
}

fn compliant_example(extended: &Extended) {
   let extended = extended as *const Extended;
   with_base(unsafe { &*(extended as *const Base) })
}

fn with_base(_: &Base) {}
Guideline: Do not use an integer type as a divisor during integer division gui_7y0GAMmtMhch
status: draft
tags: numerics, subset
category: advisory
decidability: decidable
scope: module
release: latest

Do not provide a right operand of integer type during a division expression or remainder expression when the left operand also has integer type.

This rule applies to the following primitive integer types:

  • i8

  • i16

  • i32

  • i64

  • i128

  • u8

  • u16

  • u32

  • u64

  • u128

  • usize

  • isize

Rationale: rat_vLFlPWSCHRje
status: draft
parent needs: gui_7y0GAMmtMhch

Integer division and integer remainder division both panic when the right operand has a value of zero. Division by zero is undefined in mathematics because it leads to contradictions and there is no consistent value that can be assigned as its result.

Non-Compliant Example: non_compl_ex_0XeioBrgfh5z
status: draft
parent needs: gui_7y0GAMmtMhch

Both the division and remainder operations in this non-compliant example will panic if evaluated because the right operand is zero.

compile_fail

fn main() {
    let x = 0;
    let _y = 5 / x; // This line will panic.
    let _z = 5 % x; // This line would also panic.
}
Compliant Example: compl_ex_k1CD6xoZxhXb
status: draft
parent needs: gui_7y0GAMmtMhch

Checked division prevents division by zero from occurring. The programmer can then handle the returned std::option::Option. Using checked division and remainder is particularly important in the signed integer case, where arithmetic overflow can also occur when dividing the minimum representable value by -1.

fn main() {
    // Using the checked division API
    let _y = match 5i32.checked_div(0) {
        None => 0,
        Some(r) => r,
    };

    // Using the checked remainder API
    let _z = match 5i32.checked_rem(0) {
        None => 0,
        Some(r) => r,
    };
}
Compliant Example: compl_ex_k1CD6xoZxhXc
status: draft
parent needs: gui_7y0GAMmtMhch

This compliant solution creates a divisor using std::num::NonZero. std::num::NonZero is a wrapper around primitive integer types that guarantees the contained value is never zero. std::num::NonZero::new creates a new binding that represents a value that is known not to be zero. This ensures that functions operating on its value can correctly assume that they are not being given zero as their input.

Note that the test for arithmetic overflow that occurs when dividing the minimum representable value by -1 is unnecessary in this compliant example because the result of the division expression is an unsigned integer type.

 :version: 1.79

use std::num::NonZero;

fn main() {
    let x = 0u32;
    if let Some(divisor) = NonZero::<u32>::new(x) {
        let _result = 5u32 / divisor;
    }
}
Guideline: Do not divide by 0 gui_kMbiWbn8Z6g5
status: draft
tags: numerics, defect
category: required
decidability: undecidable
scope: system
release: latest

Integer division by zero results in a panic. This includes both division expressions and remainder expressions.

Division and remainder expressions on signed integers are also susceptible to arithmetic overflow. Overflow is covered in full by the guideline Ensure that integer operations do not result in arithmetic overflow.

This rule applies to the following primitive integer types:

  • i8

  • i16

  • i32

  • i64

  • i128

  • u8

  • u16

  • u32

  • u64

  • u128

  • usize

  • isize

This rule does not apply to evaluation of the core::ops::Div trait on types other than integer types.

This rule is a less strict version of Do not use an integer type as a divisor during integer division. All code that complies with that rule also complies with this rule.

Rationale: rat_h84NjY2tLSBW
status: draft
parent needs: gui_kMbiWbn8Z6g5

Integer division by zero results in a panic; an abnormal program state that may terminate the process and must be avoided.

Non-Compliant Example: non_compl_ex_LLs3vY8aGz0F
status: draft
parent needs: gui_kMbiWbn8Z6g5

This non-compliant example panics when the right operand is zero for either the division or remainder operations.

compile_fail

fn main() {
    let x = 0;
    let _y = 5 / x; // Results in a panic.
    let _z = 5 % x; // Also results in a panic.
}
Compliant Example: compl_ex_Ri9pP5Ch3kcc
status: draft
parent needs: gui_kMbiWbn8Z6g5

Compliant examples from Do not use an integer type as a divisor during integer division are also valid for this rule. Additionally, the check for zero can be performed manually, as in this compliant example. However, as the complexity of the control flow leading to the invariant increases, it becomes increasingly harder for both programmers and static analysis tools to reason about it.

Note that the test for arithmetic overflow is not necessary for unsigned integers.

fn main() {
    // Checking for zero by hand
    let x = 0u32;
    let _y = if x != 0u32 {
        5u32 / x
    } else {
        0u32
    };

    let _z = if x != 0u32 {
        5u32 % x
    } else {
        0u32
    };
}
Guideline: The 'as' operator should not be used with numeric operands gui_ADHABsmK9FXz
status: draft
tags: subset, reduce-human-error
category: advisory
decidability: decidable
scope: module
release: <TODO>

The binary operator as should not be used with:

  • a numeric type, including all supported integer, floating, and machine-dependent arithmetic types; or

  • bool; or

  • char

as either the right operand or the type of the left operand.

Exception: as may be used with usize as the right operand and an expression of raw pointer type as the left operand.

Rationale: rat_v56bjjcveLxQ
status: draft
parent needs: gui_ADHABsmK9FXz

Although the conversions performed by as between numeric types are all well-defined, as coerces the value to fit in the destination type, which may result in unexpected data loss if the value needs to be truncated, rounded, or produce a nearest possible non-equal value.

Although some conversions are lossless, others are not symmetrical. Instead of relying on either a defined lossy behaviour or risking loss of precision, the code can communicate intent by using Into or From and TryInto or TryFrom to signal which conversions are intended to perfectly preserve the original value, and which are intended to be fallible. The latter cannot be used from const functions, indicating that these should avoid using fallible conversions.

A pointer-to-address cast does not lose value, but will be truncated unless the destination type is large enough to hold the address value. The usize type is guaranteed to be wide enough for this purpose.

A pointer-to-address cast is not symmetrical because the resulting pointer may not point to a valid object, may not point to an object of the right type, or may not be properly aligned. If a conversion in this direction is needed, std::mem::transmute will communicate the intent to perform an unsafe operation.

Non-Compliant Example: non_compl_ex_hzGUYoMnK59w
status: draft
parent needs: gui_ADHABsmK9FXz

as used here can change the value range or lose precision. Even when it doesn’t, nothing enforces the correct behaviour or communicates whether we intend to allow lossy conversions, or only expect valid conversions.

fn f1(x: u16, y: i32, z: u64, w: u8) {
  let _a = w as char;           // non-compliant
  let _b = y as u32;            // non-compliant - changes value range, converting negative values
  let _c = x as i64;            // non-compliant - could use .into()

  let d = y as f32;            // non-compliant - lossy
  let e = d as f64;            // non-compliant - could use .into()
  let _f = e as f32;            // non-compliant - lossy

  let _g = e as i64;            // non-compliant - lossy despite object size

  let b: u32 = 0;
  let p1: * const u32 = &b;
  let _a1 = p1 as usize;        // compliant by exception
  let _a2 = p1 as u16;          // non-compliant - may lose address range
  let _a3 = p1 as u64;          // non-compliant - use usize to indicate intent

  let a1 = p1 as usize;
  let _p2 = a1 as * const u32;  // non-compliant - prefer transmute
  let a2 = p1 as u16;
  let _p3 = a2 as * const u32;  // non-compliant (and most likely not in a valid address range)
}
Compliant Example: compl_ex_uilHTIOgxD37
status: draft
parent needs: gui_ADHABsmK9FXz

Valid conversions that are guaranteed to preserve exact values can be communicated better with into() or from(). Valid conversions that risk losing value, where doing so would be an error, can communicate this and include an error check, with try_into or try_from. Other forms of conversion may find transmute better communicates their intent.

use std::convert::TryInto;

fn f2(x: u16, y: i32, _z: u64, w: u8) {
  let _a: char            = w.into();
  let _b: Result <u32, _> = y.try_into(); // produce an error on range clip
  let _c: i64             = x.into();

  let d = f32::from(x);  // u16 is within range, u32 is not
  let _e = f64::from(d);
  // let f = f32::from(e); // no From exists

  // let g = ...            // no From exists

  let h: u32 = 0;
  let p1: * const u32 = &h;
  let a1 = p1 as usize;     // (compliant)

  unsafe {
    let _a2: usize = std::mem::transmute(p1);  // OK
    let _a3: u64   = std::mem::transmute(p1);  // OK, size is checked
    // let a3: u16   = std::mem::transmute(p1);  // invalid, different sizes

    let _p2: * const u32 = std::mem::transmute(a1); // OK
    let _p3: * const u32 = std::mem::transmute(a1); // OK
  }

  unsafe {
    // does something entirely different,
    // reinterpreting the bits of z as the IEEE bit pattern of a double
    // precision object, rather than converting the integer value
    let _f1: f64 = std::mem::transmute(_z);
  }
}
Guideline: An integer shall not be converted to a pointer gui_PM8Vpf7lZ51U
status: draft
tags: subset, undefined-behavior
category: <TODO>
decidability: decidable
scope: module
release: <TODO>

The as operator shall not be used with an expression of numeric type as the left operand, and any pointer type as the right operand.

std::mem::transmute shall not be used with any numeric type (including floating point types) as the argument to the Src parameter, and any pointer type as the argument to the Dst parameter.

Rationale: rat_YqhEiWTj9z6L
status: draft
parent needs: gui_PM8Vpf7lZ51U

A pointer created from an arbitrary arithmetic expression may designate an invalid address, including an address that does not point to a valid object, an address that points to an object of the wrong type, or an address that is not properly aligned. Use of such a pointer to access memory will result in undefined behavior.

The as operator also does not check that the size of the source operand is the same as the size of a pointer, which may lead to unexpected results if the address computation was originally performed in a differently-sized address space.

While as can notionally be used to create a null pointer, the functions core::ptr::null and core::ptr::null_mut are the more idiomatic way to do this.

Non-Compliant Example: non_compl_ex_0ydPk7VENSrA
status: draft
parent needs: gui_PM8Vpf7lZ51U

Any use of as or transmute to create a pointer from an arithmetic address value is non-compliant:

fn f1(x: u16, y: i32, z: u64, w: usize) {
  let _p1 = x as * const u32;  // not compliant
  let _p2 = y as * const u32;  // not compliant
  let _p3 = z as * const u32;  // not compliant
  let _p4 = w as * const u32;  // not compliant despite being the right size

  let _f: f64 = 10.0;
  // let p5 = f as * const u32;  // not valid

  unsafe {
    // let p5: * const u32 = std::mem::transmute(x);  // not valid
    // let p6: * const u32 = std::mem::transmute(y);  // not valid

    let _p7: * const u32 = std::mem::transmute(z); // not compliant
    let _p8: * const u32 = std::mem::transmute(w); // not compliant

    let _p9: * const u32 = std::mem::transmute(_f); // not compliant, and very strange
  }
}
Compliant Example: compl_ex_oneKuF52yzrx
status: draft
parent needs: gui_PM8Vpf7lZ51U

There is no compliant example of this operation.

Guideline: An integer shall not be converted to an invalid pointer gui_iv9yCMHRgpE0
status: draft
tags: defect, undefined-behavior
category: <TODO>
decidability: undecidable
scope: system
release: <TODO>

An expression of numeric type shall not be converted to a pointer if the resulting pointer is incorrectly aligned, does not point to an entity of the referenced type, or is an invalid representation.

Rationale: rat_OhxKm751axKw
status: draft
parent needs: gui_iv9yCMHRgpE0

The mapping between pointers and integers must be consistent with the addressing structure of the execution environment. Issues may arise, for example, on architectures that have a segmented memory model.

Non-Compliant Example: non_compl_ex_CkytKjRQezfQ
status: draft
parent needs: gui_iv9yCMHRgpE0

This example makes assumptions about the layout of the address space that do not hold on all platforms. The manipulated address may have discarded part of the original address space, and the flag may silently interfere with the address value. On platforms where pointers are 64-bits this may have particularly unexpected results.

fn f1(flag: u32, ptr: * const u32) {
  /* ... */
  let mut rep = ptr as usize;
  rep = (rep & 0x7fffff) | ((flag as usize) << 23);
  let _p2 = rep as * const u32;
}
Compliant Example: compl_ex_oBoluiKSvREu
status: draft
parent needs: gui_iv9yCMHRgpE0

This compliant solution uses a struct to provide storage for both the pointer and the flag value. This solution is portable to machines of different word sizes, both smaller and larger than 32 bits, working even when pointers cannot be represented in any integer type.

struct PtrFlag {
  pointer: * const u32,
  flag: u32
}

fn f2(flag: u32, ptr: * const u32) {
  let _ptrflag = PtrFlag {
    pointer: ptr,
    flag: flag
  };
  /* ... */
}
Guideline: Do not shift an expression by a negative number of bits or by greater than or equal to the bitwidth of the operand gui_RHvQj8BHlz9b
status: draft
tags: numerics, reduce-human-error, maintainability, surprising-behavior, subset
category: advisory
decidability: decidable
scope: module
release: 1.7.0-latest

Shifting negative positions or a value greater than or equal to the width of the left operand in shift left and shift right expressions are defined by this guideline to be out-of-range shifts. The Rust FLS incorrectly describes this behavior as <arithmetic overflow.

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;

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 INT34-C. Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the left operand.

Rationale: rat_3MpR8QfHodGT
status: draft
parent needs: gui_RHvQj8BHlz9b

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.

Non-Compliant Example: non_compl_ex_O9FZuazu3Lcn
status: draft
parent needs: gui_RHvQj8BHlz9b

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

fn main() {
    let bits : u32 = 61;
    let shifts = vec![-1, 4, 40];

    for sh in shifts {
        println!("{bits} << {sh} = {:?}", bits << sh);
    }
}
Non-Compliant Example: non_compl_ex_mvkgTL3kulZ5
status: draft
parent needs: gui_RHvQj8BHlz9b

This noncompliant example test 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);
        }
     }
}
Non-Compliant Example: non_compl_ex_O9FZuazu3Lcm
status: draft
parent needs: gui_RHvQj8BHlz9b

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));
    }
}
Non-Compliant Example: non_compl_ex_O9FZuazu3Lcx
status: draft
parent needs: gui_RHvQj8BHlz9b

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.

 :version: 1.87

fn main() {
    let bits : u32 = 61;
    let shifts = vec![4, 40];

    for sh in shifts {
        println!("{bits} << {sh} = {:?}", bits.unbounded_shr(sh));
    }
}
Non-Compliant Example: non_compl_ex_O9FZuazu3Lcp
status: draft
parent needs: gui_RHvQj8BHlz9b

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}");
        }
    }
}
Compliant Example: compl_ex_xpPQqYeEPGIo
status: draft
parent needs: gui_RHvQj8BHlz9b

This compliant example performs left shifts via the checked_shl function and right shifts via the 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));
    }
}
Guideline: Avoid out-or-range shifts gui_LvmzGKdsAgI5
status: draft
tags: numerics, surprising-behavior, defect
category: mandatory
decidability: undecidable
scope: module
release: 1.0.0-latest

Shifting negative positions or a value greater than or equal to the width of the left operand in shift left and shift right expressions are defined by this guideline to be out-of-range shifts. The Rust FLS incorrectly describes this behavior as <arithmetic overflow.

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;

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 a less strict but undecidable version of Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the operand. All code that complies with that rule also complies with this rule.

This rule is based on The CERT C Coding Standard Rule INT34-C. Do not shift an expression by a negative number of bits or by greater than or equal to the number of bits that exist in the left operand.

Rationale: rat_tVkDl6gOqz25
status: draft
parent needs: gui_LvmzGKdsAgI5

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.

Non-Compliant Example: non_compl_ex_KLiDMsCesLx7
status: draft
parent needs: gui_LvmzGKdsAgI5

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

fn main() {
    let bits : u32 = 61;
    let shifts = vec![-1, 4, 40];

    for sh in shifts {
        println!("{bits} << {sh} = {:?}", bits << sh);
    }
}
Compliant Example: compl_ex_Ux1WqHbGKV73
status: draft
parent needs: gui_LvmzGKdsAgI5

This compliant example test 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);
        }
     }
}
Compliant Example: compl_ex_Ux1WqHbGKV74
status: draft
parent needs: gui_LvmzGKdsAgI5

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 safe_shl(bits: u32, shift: u32) -> u32 {
    let (result, overflowed) = bits.overflowing_shl(shift);
    if overflowed {
        0
    } else {
        result
    }
}

fn main() {
    let bits: u32 = 61;
    let shifts = vec![4, 40];

    for sh in shifts {
        let result = safe_shl(bits, sh);
        println!("{bits} << {sh} = {result}");
    }
}