Step 3 of 7
When numbers overflow
Every integer type has a fixed range. What happens when a calculation goes past it? That's called integer overflow, and in many languages it silently produces a wrong number (a classic source of security bugs).
Rust doesn't let it pass silently:
- If the compiler can see the overflow, it refuses to compile.
- In a debug build (
cargo run, and on this site), an overflow at run time makes the program panic. - In a release build (
cargo build --release), the checks are switched off for speed and the value wraps around: 255 + 1 in au8becomes 0.
fn main() {
let level: u8 = 255;
let next = level + 1;
println!("{next}");
}
error: this arithmetic operation will overflow
--> src/main.rs:3:16
|
3 | let next = level + 1;
| ^^^^^^^^^ attempt to compute `u8::MAX + 1_u8`, which would overflow
When the values change while the program runs, the compiler can't see the problem, so the debug build checks at run time:
fn main() {
let mut level: u8 = 250;
println!("level {level}");
level += 10;
println!("level {level}");
}
level 250
thread 'main' panicked at src/main.rs:4:5:
attempt to add with overflow
Choosing what should happen
A panic isn't always what you want, so every integer type has methods that say explicitly how to handle overflow. A method is a function that belongs to a value, called with a dot: value.method(argument).
fn main() {
let hp: u8 = 250;
println!("{}", hp.saturating_add(10));
println!("{}", hp.wrapping_add(10));
println!("{:?}", hp.checked_add(10));
println!("{:?}", hp.checked_add(5));
}
255
4
None
Some(255)
saturating_addstops at the limit: 255. Good for volume, health bars, anything that caps.wrapping_addwraps around like a car's odometer: 260 - 256 = 4. Used in checksums, hashing and random number generators.checked_addgives backSome(answer)if the answer fits, orNoneif it would overflow. This is anOption, Rust's way of saying "maybe a value". You'll learn to work with it properly later; for now, print it with{:?}.
There are versions for each operation: _sub, _mul, _div. For unsigned types, saturating_sub stops at 0:
fn main() {
let stock: u32 = 3;
println!("{}", stock.saturating_sub(5));
println!("{:?}", stock.checked_sub(5));
println!("{:?}", stock.checked_sub(2));
}
0
None
Some(1)
At work
When values come from outside the program (user input, files, the network), a plain + can panic in debug builds or wrap in release builds. Careful code uses checked_ where overflow means bad data and saturating_ where capping is the intended behavior. Often the simplest fix is a big enough type: money in cents as an i64 won't overflow in practice.
Your task
A music player's volume is a u8. Starting from volume = 245, print the volume after turning it up by 20 (it should stop at 255), then print what checked_add says about adding 20:
255
None
Challenge 2
A shop's stock can't go below zero. Fill in the method so selling 7 items from a stock of 4 leaves 0 instead of panicking.
Challenge 3
An 8-bit counter ticks is at 200. Print ticks + 100 three ways, one per line: wrapping, saturating and checked:
44
255
None
Previous: Integer division and remainder Next: Floating-point numbers