Why does Rust perform integer overflow checks in --release?

Solution 1:

The book is slightly imprecise. Overflow is disallowed in both debug and release modes, it's just that release mode omits runtime checks for performance reasons (replacing them with overflow, which CPUs typically do anyway). Static checks are not removed because they don't compromise on performance of generated code. This prints 0 in release mode and panics in debug1:

let x: u8 = "255".parse().unwrap();
let val: u8 = x + 1;
println!("{}", val);

You can disable the compile-time checks using #[allow(arithmetic_overflow)]. This also prints 0 in release mode and panics in debug:

#[allow(arithmetic_overflow)]
let val: u8 = 255 + 1;
println!("{}", val);

The correct approach is to not depend on this behavior of release mode, but to tell the compiler what you want. This prints 0 in both debug and release mode:

let val: u8 = 255u8.wrapping_add(1);
println!("{}", val);

1 The example uses "255".parse() because, to my surprise, let x = 255u8; let val = x + 1; doesn't compile - in other words, rustc doesn't just prevent overflow in constant arithmetic, but also wherever else it can prove it happens. The change was apparently made in Rust 1.45, because it compiled in Rust 1.44 and older. Since it broke code that previously compiled, the change was technically backward-incompatible, but presumably broke sufficiently few actual crates that it was deemed worth it. Surprising as it is, it's quite possible that "255".parse::<u8>() + 1 will become a compile-time error in a later release.

Solution 2:

In your code, the compiler is able to detect the problem. That's why it prevents it even in release mode. In many cases it's not possible or feasible for the compiler to detect or prevent an error.

Just as an example, imagine you have code like this:

let a = b + 5;

Let's say b's value comes from a database, user input or some other external source. It is literally impossible to prevent overflows in cases like that.