Reputation: 203
fn main() {
let num: u8 = 255;
let num2: u8 = num + 1;
println!("{}, {}", num, num2);
}
When $ cargo build --release
, this code doesn't make compile error.
And $ cargo run
, make runtime error.
thread 'main' panicked at 'attempt to add with overflow', src/main.rs:3:20 note: run with
RUST_BACKTRACE=1
environment variable to display a backtrace
This is okay. But what I don't understand is the situation below. When I delete println line, it makes compile error.
fn main() {
let num: u8 = 255;
let num2: u8 = num + 1;
}
$ cargo build --release
error: this arithmetic operation will overflow
--> src/main.rs:3:20
|
3 | let num2: u8 = num + 1;
| ^^^^^^^ attempt to compute `u8::MAX + 1_u8`, which would overflow
|
= note: `#[deny(arithmetic_overflow)]` on by default
Why does integer overflow sometimes cause compilation errors or runtime error?
Upvotes: 20
Views: 2057
Reputation: 27187
It's going to be a compile time error when the compiler can trivially prove it's going to overflow at runtime, that happens when you remove the println
because then num
can be inlined easily (it's only used in that one spot anyways), but println
is very hard to optimize around because it takes references to it's arguments and it's not easily provable that different addresses don't make a difference for it (consider also there is a fmt::Pointer
) all of this leads to the fact that it's not as trivial to prove for the first case where num
can't be inlined that easily.
For reference here are the mir representations of the first and second variable in each variant where you can see that in one the variable is replaced with u8::MAX
already:
println
:
[...]
bb0: {
_1 = const u8::MAX; // scope 0 at plain_overflow.rs:2:19: 2:22
_3 = const u8::MAX; // scope 1 at plain_overflow.rs:3:20: 3:23
_4 = CheckedAdd(_3, const 1_u8); // scope 1 at plain_overflow.rs:3:20: 3:27
assert(!move (_4.1: bool), "attempt to compute `{} + {}`, which would overflow", move _3, const 1_u8) -> bb1; // scope 1 at main.rs:3:20: 3:27
}
[...]
println
:
[...]
bb0: {
_1 = const u8::MAX; // scope 0 at with_print.rs:2:19: 2:22
_3 = _1; // scope 1 at with_print.rs:3:20: 3:23
_4 = CheckedAdd(_3, const 1_u8); // scope 1 at with_print.rs:3:20: 3:27
assert(!move (_4.1: bool), "attempt to compute `{} + {}`, which would overflow", move _3, const 1_u8) -> bb1; // scope 1 at with_print.rs:3:20: 3:27
}
[...]
where in both cases _1
, _3
and _4
correspond to num
, the value of num
in the line where num2
is assigned and the result of a checked additon of num
and 1
respectively.
After some more experimentation, not println
is the culprit but merely taking a reference to num
Upvotes: 9
Reputation: 371
Compiler warnings are produced by linting and it might be hard to statically check the arithmetic overflow in all cases. In this case, println!()
is somehow making it difficult for the compiler to detect the overflow. I think it might be worth reporting this as a bug?
Also, the reason why the code produced a runtime error is that you didn't run it in release
mode. Try running the same code as cargo run --release
and then you'll get output as
255, 0
This is because, in release
mode, Rust does not include checks for arithmetic overflow that cause panics. Instead, if overflow occurs, Rust performs two’s complement wrapping.
Upvotes: 2