Rust Data Types

Integer Overflow

Integer overflow occurs when an arithmetic operation on a fixed-width integer type produces a mathematical result outside that type's representable range (MIN..=MAX)—either exceeding MAX (overflow) or falling below MIN (underflow). Unlike C and C++, where signed integer overflow is Undefined Behavior that allows the compiler to miscompile security checks, Rust defines exact, deterministic rules for integer overflow and provides four explicit families of arithmetic methods.

Default Overflow Behavior: Debug vs Release Builds

When you use standard arithmetic operators (+, -, *, unary -, <<, >>), Rust's behavior depends on the build profile:

Build Mode overflow-checks Setting Behavior on +, -, * Overflow
Debug build (cargo build / cargo run) true Panics immediately (attempt to add with overflow)
Release build (cargo build --release) false Wraps around using two's complement modular arithmetic (never UB!)
Compile-time constant (const or literal) Always checked Compile-time error (#[deny(arithmetic_overflow)])
8-bit Unsigned (`u8`, range 0..=255) Overflow at 255 + 1:
  - In Debug Mode:     Panics at runtime (`attempt to compute 255_u8 + 1_u8, which would overflow`)
  - In Release Mode:   Wraps around the modular clock wheel: 255 -> 0 (`255 + 2` -> `1`)

          0
      255   1
    254       2
      ...   ...
         128

The Four Explicit Overflow Method Families

Rather than relying on build-profile differences (debug vs release), Rust provides four explicit method families on every integer type (u8..u128, usize, i8..i128, isize) that behave identically in both debug and release builds:

Method Family Suffix Examples Return Type Behavior on Overflow (Debug AND Release)
wrapping_* wrapping_add, wrapping_sub, wrapping_mul, wrapping_neg, wrapping_shl T Wraps around modularly ($2^N$) using two's complement
checked_* checked_add, checked_sub, checked_mul, checked_div, checked_neg Option<T> Returns Some(result) if in range, or None if overflow occurred
overflowing_* overflowing_add, overflowing_sub, overflowing_mul, overflowing_neg (T, bool) Returns a tuple of (wrapped_result, did_overflow_bool)
saturating_* saturating_add, saturating_sub, saturating_mul, saturating_pow T Clamps at the boundary (T::MAX on upper overflow, T::MIN on lower underflow)
fn main() {
    let a: u8 = 250;
    let b: u8 = 10; // Mathematical sum is 260 (exceeds u8::MAX = 255 by 5)

    // 1. Wrapping: 260 mod 256 = 4
    println!("wrapping_add:    {}", a.wrapping_add(b));

    // 2. Checked: returns `None` on overflow
    println!("checked_add:     {:?}", a.checked_add(b));
    println!("checked_add(5):  {:?}", a.checked_add(5));

    // 3. Overflowing: returns both the wrapped value (4) and overflow flag (`true`)
    println!("overflowing_add: {:?}", a.overflowing_add(b));

    // 4. Saturating: clamps at `u8::MAX` (255)
    println!("saturating_add:  {}", a.saturating_add(b));
    println!("saturating_sub:  {}", 5_u8.saturating_sub(20)); // Clamps at `u8::MIN` (0)
}
wrapping_add:    4
checked_add:     None
checked_add(5):  Some(255)
overflowing_add: (4, true)
saturating_add:  255
saturating_sub:  0

Choosing the Right Method Family

Domain / Scenario Recommended Family Why
Parsing user input, financial totals, buffer size calculations checked_* (with ? or .ok_or(...)) Detects overflow explicitly so corrupt or malicious inputs cannot wrap into small allocations
Hashing algorithms, PRNGs, cryptography, emulators wrapping_* (or std::num::Wrapping<T>) Intentional modular $2^N$ arithmetic
Audio DSP samples, pixel brightness (u8), UI progress bars, timers saturating_* (or std::num::Saturating<T>) Clamping 250 + 20 to 255 (white) avoids wrapping a bright pixel to 14 (black)
Multi-word big-integer arithmetic (carrying bit into next word) overflowing_* The boolean flag directly supplies the carry/borrow bit

Wrapper Types: Wrapping<T> and Saturating<T>

When writing an algorithm (such as a hash function or signal filter) that performs dozens of wrapping or saturating operations using +, -, and *, calling .wrapping_add() on every step can clutter the formula.

The standard library provides two zero-cost wrapper structs in std::num—Wrapping<T> and Saturating<T>—that overload the standard +, -, * operators to always wrap or always saturate:

use std::num::{Saturating, Wrapping};

fn main() {
    // 1. Wrapping arithmetic using standard `+` and `*` operators:
    let w1 = Wrapping(200_u8);
    let w2 = Wrapping(100_u8);
    let wrapped_sum: Wrapping<u8> = w1 + w2; // 300 mod 256 = 44
    println!("Wrapping(200) + Wrapping(100) = {}", wrapped_sum.0);

    // 2. Saturating arithmetic using standard `+` and `-` operators:
    let s1 = Saturating(200_u8);
    let s2 = Saturating(100_u8);
    println!("Saturating(200) + Saturating(100) = {}", (s1 + s2).0);
    println!("Saturating(50)  - Saturating(100) = {}", (Saturating(50_u8) - s2).0);
}
Wrapping(200) + Wrapping(100) = 44
Saturating(200) + Saturating(100) = 255
Saturating(50)  - Saturating(100) = 0

Signed MIN Division and Negation Overflow

For signed integers (i8..i128, isize), two's complement representation has one asymmetric edge case: |MIN| is 1 greater than MAX (for example, i8::MIN is -128, while i8::MAX is +127).

Consequently, two operations on MIN always overflow a signed integer:

  1. Negating MIN (-i8::MIN or i8::MIN.abs()): Because +128 cannot fit in i8.
  2. Dividing MIN by -1 (i8::MIN / -1 or i8::MIN % -1): In Rust, MIN / -1 panics in both debug AND release builds because hardware integer division instructions (idiv on x86_64) trap with a CPU exception on MIN / -1! Use x.checked_div(y) or x.wrapping_div(y) when dividing untrusted signed inputs.