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
}