Overflow and Wrapping

C++ assumes wrapping, but undefined behavior (which allows for some great compiler optimizations, but is also confusing). Rust considers wrapping and overflow to be well-defined, but behavior you should specify.

You're probably used to this behavior from C++ (it's in the cpp/byte_overflow directory):

#include <iostream>
#include <cstdint>

int main() {
    uint8_t j = 0;
    for (int i = 0; i < 512; i++) {
        j++;
        std::cout << i << " : " << unsigned(j) << std::endl;
    }
    return 0;
}

This outputs 0 to 255 twice.

In Rust, the same program:

fn main() {
    let mut j: u8 = 0;
    for i in 0..512 {
        j += 1;
        println!("{i} : {j}");
    }
}

Running the program panics---crashes. It gives the error message "attempt to add with overflow".

Note that running in release mode (cargo run --release) skips his run-time check for performance. It's a great idea to run in debug mode sometimes.

Opting in to Wrapping

If your algorithm expects wrapping behavior, the easiest option is to use the wrapping_add function. That makes it clear that you expect wrapping, and acts appropriately:

fn main() {
    let mut j: u8 = 0;
    for i in 0..512 {
        j = j.wrapping_add(1);
        println!("{i} : {j}");
    }
}

If you'd just like to detect that wrapping would have occurred, you can use:

fn main() {
    let mut j: u8 = 0;
    for i in 0..512 {
        j = j.checked_add(1).unwrap(); // Returns `None` or `Some(result)`
        println!("{i} : {j}");
    }
}

This program will crash even on release-mode, because we've used unwrap - which deliberately panics on an error. You could detect the problem, choose how to handle it, and not crash.

You can use saturating functions also:

fn main() {
    let mut j: u8 = 0;
    for i in 0..512 {
        j = j.saturating_add(1);
        println!("{i} : {j}");
    }
}

If you don't like the extra typing, and want to associate a behavior with a type, you can use saturating and wrappign types.

use std::num::Wrapping;

fn main() {
    let mut j: Wrapping<u8> = Wrapping(0);
    for i in 0..512 {
        j += Wrapping(1);
        println!("{i} : {j}");
    }
}

Rust is being very explicit about behavior, because surprises are a bad thing!