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:
- Negating
MIN(-i8::MINori8::MIN.abs()): Because+128cannot fit ini8. - Dividing
MINby-1(i8::MIN / -1ori8::MIN % -1): In Rust,MIN / -1panics in both debug AND release builds because hardware integer division instructions (idivon x86_64) trap with a CPU exception onMIN / -1! Usex.checked_div(y)orx.wrapping_div(y)when dividing untrusted signed inputs.