Junhee
Junhee

Reputation: 203

In Rust, Why does integer overflow sometimes cause compilation error or runtime error?

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

Answers (2)

cafce25
cafce25

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:

  • without 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
        }
    [...]
    
  • with 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

anand
anand

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

Related Questions