Skip to content

← Rust: zero to hero step 9 of 342

Medium Primitives

checked, wrapping, saturating, overflowing

Add the values in a slice from left to right. Return None as soon as an addition would put the running total outside the i32 range.

pub fn safe_sum(values: &[i32]) -> Option<i32>

Start at zero. If every addition fits, return Some(total). The empty slice returns Some(0).

Input Result
[1, 2, 3] Some(6)
[i32::MAX, 1] None
[i32::MIN, -1] None
[i32::MAX, 1, -1] None

The last case matters: the final mathematical sum fits, but an intermediate sum does not. Do not add everything in a wider type and check only at the end. Each prefix of the input must have a representable total.

Choose the overflow behavior

The starter uses total += v. If overflow checks are enabled, an overflowing addition panics. If they are disabled, it wraps. Neither behavior returns the None required here. Cargo's default development and release profiles differ in their overflow-check setting, but that setting can be overridden.

Integer methods let you choose a behavior explicitly. Here are four useful addition methods:

let n = 250u8;
assert_eq!(n.checked_add(10), None);
assert_eq!(n.wrapping_add(10), 4);
assert_eq!(n.saturating_add(10), 255);
assert_eq!(n.overflowing_add(10), (4, true));
  • checked_add returns None if the result does not fit. Use it here to let the caller handle overflow.
  • wrapping_add computes modulo the integer type's range. For u8, that means modulo 256. This is useful when an algorithm requires modular arithmetic.
  • saturating_add stops at the type's minimum or maximum. Those limits are not necessarily your application's limits: a percentage stored in u8 still needs an explicit upper bound of 100.
  • overflowing_add returns the wrapped result together with a flag saying whether overflow occurred. Wider arithmetic can use that flag as a carry.

These methods behave consistently whether overflow checks are enabled or disabled. They also have subtraction and multiplication counterparts, but not every operation has every form. See the integer method documentation for the operation you need. In particular, checked_shl checks the shift count; it does not reject a shift just because nonzero bits are lost.

Keep the modulus in mind when using wrapping arithmetic. Wrapping an integer is not automatically the same as wrapping an index around a buffer of an arbitrary length.

Represent success or overflow with Option

checked_add returns Option<i32>. Some(value) contains a valid result; None represents overflow. Both are variants of the standard library's Option enum, whose basic shape is:

enum Option<T> { Some(T), None }

Option<i32> and i32 are different types. You need to handle the option before using its contained integer. For this exercise, overflow should end the calculation, so return None rather than unwrapping it and panicking. Zero is a valid sum and is represented by Some(0), not None.

Propagate None with ?

This line updates the total only when the addition succeeds:

total = total.checked_add(v)?;

Here, ? extracts the value from Some(value). On None, it immediately returns None from safe_sum. The function's Option<i32> return type supports that early return. If you put the expression inside a closure, propagation is from that closure instead, so its return type must support it.

For [i32::MAX, 1, -1], the steps are:

Next value Result of checked addition Action
i32::MAX Some(i32::MAX) Update total
1 None Return None
-1 Not evaluated Already returned

If the loop finishes, wrap the total in Some. The same rule covers negative overflow: i32::MIN.checked_add(-1) also returns None.

As a separate experiment, consider a function that uses saturating_add at each step and returns an i32. This changes the contract. For [i32::MAX, 1, -1], it returns i32::MAX - 1: once an intermediate value is clamped, a later subtraction cannot recover the lost amount. Step-by-step saturation is not the same as clamping the final mathematical sum.