7

I have this piece of simple code:

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

It is said here that such a code will compile normally if run with the --release flag.

I am running this code via cargo run --release, and I still see the checks:

error: this arithmetic operation will overflow
 --> src/main.rs:2:19
  |
2 |     let val: u8 = 255 + 1;
  |                   ^^^^^^^ attempt to compute `u8::MAX + 1_u8`, which would overflow
  |
  = note: `#[deny(arithmetic_overflow)]` on by default

error: could not compile `rust-bin` due to previous error

Am I missing something?

Oleksandr Novik
  • 489
  • 9
  • 24

2 Answers2

14

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.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • Good observation. Theoretically, the compiler can be extended to catch a lot more cases. But, of course, lots of cases could never be predicted at compile time (as I mentioned in my answer). Also, some cases that *could* be detected at compile time would probably cost way too much in terms of compilation speed and/or implementation complexity, so it simply won't be worth it to handle them. As for `"255".parse::()` - that's a rather artificial example. I can't think of many situations where we would want to parse a string literal. – at54321 Jan 19 '22 at 22:38
  • Note that you can opt-in to runtime overflow checks in release mode by setting `overflow-checks = true` in `[profile.release]` in `Cargo.toml`. doc.rust-lang.org/cargo/reference/profiles.html – pizzapants184 Dec 13 '22 at 22:29
3

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.

at54321
  • 8,726
  • 26
  • 46