Skip to content

← Ground Rules: Values, Types, Control Flow step 5 of 24

Medium Primitives

Wrap an eight-bit meter reading

Implement an eight-bit meter that wraps through the values 0..=255:

pub fn meter_readings(start: u8, deltas: &[i32]) -> Vec<u8>

Apply each signed delta to the current reading and return the reading after every update. Do not include the starting reading. Empty deltas gives an empty vector. Support the full i32 range, including negative deltas and changes larger than one revolution.

For start = 250 and deltas = [10, -20, 300]:

Current reading Delta Mathematical sum Next reading
250 10 260 4
4 -20 -16 240
240 300 540 28

The result is [4, 240, 28]. Each next reading is the mathematical sum modulo 256, expressed as a value from 0 through 255. A delta of 256 or -256 leaves the reading unchanged.

Fix the type mismatch

The starter's current += d mixes u8 and i32. Rust does not implicitly convert one integer operand to match the other, so that statement fails to compile.

For this task, d as u8 is useful: it keeps the lowest eight bits. For example:

Delta d as u8
10 10
-20 236
300 44
256 0

This is a truncating conversion, not a range check. It suits this meter because adding the truncated delta modulo 256 has the same result as adding the original delta modulo 256. For data that must fit without losing information, a checked conversion would be a different requirement.

Make wrapping explicit

Use wrapping_add to combine the current reading and the converted delta. It returns the wrapped result regardless of whether overflow checks are on. The cast does not overflow; the subsequent addition is the operation that needs an explicit wrapping policy.

Avoid computing i32::from(current) + d first: that intermediate sum can overflow when d is near i32::MAX. Converting the delta to a byte and using wrapping addition avoids that intermediate calculation.

Why ordinary addition can behave differently

For runtime integer addition, default debug builds check overflow and panic; default release builds disable those checks and wrap. These are defaults, not requirements of optimization itself. Cargo's overflow-checks setting and rustc's -C overflow-checks option can change the behavior independently of optimization level.

The site's current Rust runner uses rustc -O. Plain += can therefore produce the expected readings there while panicking in a local debug build. Explicit wrapping makes the function's behavior follow its contract in both.

Do not generalize this to every operation: integer division by zero still panics at runtime in optimized builds, for example.

Compile-time diagnostics are a separate check

This ordinary statement is rejected by rustc's default arithmetic_overflow lint because the overflow is apparent without running the code:

let x: u8 = 250 + 10;

A required constant expression such as const X: u8 = 250 + 10; also fails constant evaluation. That is distinct from a lint on an ordinary statement: allowing the lint does not make the invalid constant valid.

Intentional constant wrapping is valid when expressed explicitly:

const X: u8 = 250u8.wrapping_add(10);
assert_eq!(X, 4);

Choose arithmetic that expresses the intended behavior. This meter wraps; a different task might need checked or saturating arithmetic instead.