Rust integer types and integer overflow
i8 to i128, u8 to usize: Rust integer ranges, what happens on overflow in debug and release builds, and checked, wrapping and saturating math.
Rust has signed (i) and unsigned (u) integers of fixed sizes. The number is how many bits they use:
| Type | Range | Typical use |
|---|---|---|
u8 |
0 to 255 | bytes, small counts |
i32 |
about -2.1 billion to 2.1 billion | the default for integer literals |
u32 |
0 to about 4.3 billion | counts, ids |
i64 / u64 |
about ±9.2 × 10^18 / 0 to 1.8 × 10^19 | money in cents, timestamps, big totals |
i128 / u128 |
huge | rare: cryptography, very large math |
usize / isize |
pointer-sized (64-bit on most machines) | indexes and lengths: v.len() is a usize |
Each type has MIN and MAX constants, like u8::MAX (255) and i32::MAX (2147483647). Literals can use underscores for readability (1_000_000), other bases (0xff, 0b1010, 0o17) and a type suffix (255u8, 10_i64).
What happens on overflow
Overflow is a result that doesn't fit in the type: 250u8 + 10 would be 260, but a u8 stops at 255. What Rust does depends on the build:
- Debug builds (
cargo run,cargo test, this site): the program panics withattempt to add with overflow. You find the bug the first time it happens. - Release builds (
cargo build --release): the value wraps around (250 + 10 becomes 4) for speed, unless you turn checks on withoverflow-checks = truein yourCargo.tomlprofile.
Either way it's never undefined behavior like in C. When the compiler can see the overflow, it refuses to build:
fn main() {
let level: u8 = 256;
println!("{level}");
}
error: literal out of range for `u8`
Choosing what overflow means
When overflow is possible, say what you want with explicit methods:
fn main() {
let hp: u8 = 250;
println!("{:?}", hp.checked_add(10));
println!("{:?}", hp.checked_add(5));
println!("{}", hp.wrapping_add(10));
println!("{}", hp.saturating_add(10));
println!("{:?}", hp.overflowing_add(10));
}
None
Some(255)
4
255
(4, true)
checked_*returns anOption:Noneon overflow. Use it when overflow means bad input you should report.saturating_*clamps atMIN/MAX. Good for health bars, volume and progress counters.wrapping_*wraps around on purpose. Used in hashing, checksums and random number generators.overflowing_*returns the wrapped value and aboolsaying whether it overflowed.
Common mistakes
len() - 1on an empty collection.v.len()is ausize, which can't go below zero, sov.len() - 1on an emptyVecpanics withattempt to subtract with overflow. Usev.len().checked_sub(1),v.last(), or checkv.is_empty()first.- Using
u32for values that can be negative. A bank balance or a temperature difference needs a signed type. - Summing many
i32s. A total of large values can overflow even when each one fits. Usei64oru64for totals. - Mixing integer types.
a + bwith ani32and ani64doesn't compile; convert explicitly withi64::from(a)oras.
Example
fn main() {
println!("u8: {} to {}", u8::MIN, u8::MAX);
println!("i32: {} to {}", i32::MIN, i32::MAX);
println!("big literal: {}", 1_000_000_u64 * 1_000_000);
println!("hex 0xff = {}, binary 0b1010 = {}", 0xff, 0b1010);
let stock: u8 = 250;
match stock.checked_add(10) {
Some(n) => println!("new stock {n}"),
None => println!("stock would overflow a u8"),
}
println!("health capped at {}", stock.saturating_add(10));
let empty: Vec<i32> = Vec::new();
println!("last index of empty list: {:?}", empty.len().checked_sub(1));
}
Output:
u8: 0 to 255
i32: -2147483648 to 2147483647
big literal: 1000000000000
hex 0xff = 255, binary 0b1010 = 10
stock would overflow a u8
health capped at 255
last index of empty list: None