We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ground Rules: Values, Types, Control Flow step 5 of 24
Integer overflow: debug panics, release wraps
An eight-bit odometer. It starts at start and each delta moves it. It has 256
positions and it wraps in both directions. Report the reading after each delta.
pub fn meter_readings(start: u8, deltas: &[i32]) -> Vec<u8>
(250, [10, -20, 300]) gives [4, 240, 28]:
-
250 + 10 = 260, which does not fit in a byte, so it wraps to4; -
4 - 20 = -16, which wraps the other way to240; -
240 + 300 = 540, and540 mod 256 = 28.
The starter does not compile, and after it does you will meet a behaviour that depends on how the program was built.
The same source, two different behaviours
This is the first place in Rust where identical source code does different things under different build profiles, and it catches everybody once.
let mut x: u8 = 250;
x += 10;
-
Debug build (
cargo run,cargo test, no-O): this panics withattempt to add with overflow, and your program stops. -
Release build (
cargo run --release, orrustc -O— which is what this site uses): this wraps to4, silently, and the program carries on.
Both are defined behaviour. Neither is undefined behaviour — Rust is not C
here, and there is no nasal-demon clause. The language deliberately says
“overflow is a program bug; checking costs cycles; here is a knob.” The knob is
overflow-checks, which defaults to on in debug and off in release.
On this site the build line is rustc -O, so debug_assertions is off and
arithmetic wraps. No problem in this course can ever assert “this panics on
overflow”, because at -O it does not. That is why this problem asks you to
predict the wrap rather than to catch a panic.
The trap: constants are checked at compile time
Try to be clever and hard-code the overflow and you get a hard error, at every optimisation level:
let x: u8 = 250 + 10;
// error: this arithmetic operation will overflow
That is arithmetic_overflow, a deny-by-default rustc lint that runs during
constant evaluation. The same is true for a constant out-of-bounds index
(unconditional_panic) and for a literal division by zero. So the values in
this problem have to flow through parameters — which they do — or the compiler
would refuse the program before it ever ran.
This is a genuinely useful thing to know: Rust catches the overflows it can see at compile time, and the ones it cannot see depend on the profile.
What you have to write
Two obstacles, in order.
First, current += d does not compile: current is a u8 and d is an
i32, and Rust implements AddAssign<u8> for u8 but not AddAssign<i32>.
There is no implicit widening or narrowing in Rust, ever. You must convert.
Second, the conversion you want is d as u8, which keeps the low eight bits
and throws the rest away. That is exactly right for an odometer:
-
10 as u8is10; -
-20 as u8is236, because-20in two’s complement is…11101100, and adding236to a byte is the same as subtracting20from it; -
300 as u8is44, because300 - 256 = 44.
Once every delta is a u8, plain += gives you the wrap-around for free. Item
1.10 goes into as properly; item 1.6 shows you the methods that let you say
“I want wrapping” out loud instead of relying on the profile.
The thing to take away
If you copy this function into a cargo project and run it in debug, it will
panic on the very first test case. That is not a contradiction — it is the same
code, built with checks on. Any time a Rust program behaves differently on your
machine and in CI, ask about the profile first.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.