Skip to content

G.TYP.INT.01 整数类型的算数运算,宜使用checked, saturating_, overflowing_, wrapping_ 为前缀的方法

【级别】 建议

【描述】

使用整数计算时需结合场景和业务考虑,如发生溢出、回绕或截断时,是否会产生未定义行为或不符合 预期的结果,比如在计算时间或数组索引时发生整数溢出会导致错误结果或 Panic。

在 Rust 标准库中,提供 add / checked_add / saturating_add / overflowing_add / wrapping_add 等系列方法,可结合场合选择合适的方法:

  • checked_* 系列函数返回 Option ,一旦发生溢出则返回 None。
  • saturating_* 系列函数返回类型是整数,如果溢出,则给出该类型可表示范围的“最大/最小”值。
  • wrapping_* 系列函数则是直接抛弃已经溢出的最高位,将剩下的部分返回。即返回二进制补码结果。
  • overflowing_* 系列函数返回二进制补码结果以及指示是否发生溢出的布尔值。

【说明】

Rust 编译器在编译时默认没有溢出检查(可通过编译参数来引入),但在运行时会有 Rust 内置 lint (# [deny(arithmetic_overflow)])来检查,如果有溢出会 Panic。

无符号整数使用时要注意回绕(wraparound) ,不同整数类型转换时需注意截断。

整数溢出(Overflow):是指在计算机中使用固定位数的二进制来表示整数时,当进行加减乘等运算 时,如果得到的结果超出了该类型的范围,发生溢出现象。例如,如果使用 u8 类型的整数表示范围为 0 - 255 的无符号整数,当进行加法运算时,如果两个加数之和超过了 255,就会发生溢出现象,结果会变 为 0 - 254 范围内的某个值,而不是正确的结果。

整数回绕(Wraparound):是指当一个整数超出了其类型的取值范围时,其值会被回绕到类型的取值 范围内的最小值或最大值。例如,在使用 u8 类型表示无符号整数时,如果对一个值为 255 的整数进行 加 1 操作,得到的结果会变为 0,因为 0 是 u8 类型的最小值,此时发生了回绕现象。

【反例】

Rust
#![warn(clippy::integer_arithmetic)]

// 不符合
assert_eq!((-5i32).abs(), 5); assert_eq!(100i32+1, 101);

fn test_integer_overflow() {
  // 不符合:这种写法,Rust 编译器不检查,但 Clippy 可以检查到 // debug 模式,发生运行时 panic
  // release 模式,输出 x = 0
  let mut x: u8 = 255; x += 1;
  println!("x={}", x); 
}

【正例】

Rust
#![warn(clippy::integer_arithmetic)]

// 符合
assert_eq!((-5i32).checked_abs(), Some(5)); 
assert_eq!(100i32.saturating_add(1), 101);

fn test_integer_overflow() { let x: u8 = 255;
  // 符合:输出 x = 0
  println!("x={}", x.wrapping_add(1));
  // 符合:输出 x = 255
  println!("x={}", x.saturating_add(1)); 
}

【反例】

Rust
#![feature(unchecked_math)]
// 不符合:不安全的移位操作
fn main() {
  let a: u8 = 1;
  let a_shl = unsafe { a.unchecked_shl(8) }; 
  println!("{a_shl}"); // 输出:1
}

【正例】

Rust
// 符合:使用安全的移位操作
fn main() {
  let a: u8 = 1;
  let a_shl = a.wrapping_shl(8);
  println!("{a_shl}"); // 输出:0 
}