🎓 A1Learner
🔍

Rust Overflow in Debug and Release

Overflow checks are checks that stop a program when a number result does not fit its type. The same sum can panic in one build and wrap in the other.

When the compiler can see it coming

Written with plain numbers the compiler can check, an overflowing sum never runs at all. This code is refused while compiling, with the error "this arithmetic operation will overflow":

let m: u8 = 255;
let total = m + 1;
println!("total = {}", total);

When the number arrives while running

Hide the value from the compiler, and it can no longer see the overflow coming. black_box keeps the number a secret until the program runs. Build this program the usual way and it panics with "attempt to add with overflow". Build it with --release and it calmly prints total = 0:

fn main() {
    let m = std::hint::black_box(255u8);
    let total = m + 1;
    println!("total = {}", total);
}

Ask the program which build it is

cfg! asks the compiler a yes-or-no question while compiling. In a debug build this prints true. In a release build the same program prints false:

let checks = cfg!(debug_assertions);
println!("overflow checks on = {}", checks);

Turn the checks on for release builds

Two lines in your project's Cargo.toml keep the checks in release builds. Release builds then panic on overflow, just like debug builds:

[profile.release]
overflow-checks = true

Example

fn main() {
    let checks_on = cfg!(debug_assertions);
    println!("overflow checks on = {}", checks_on);
    let m: u8 = 255;
    println!("checked = {:?}", m.checked_add(1));
    println!("wrapped = {}", m.wrapping_add(1));
}

Key points

  • A debug build is what cargo run and cargo build make.
  • Overflow checks are on.
  • An overflowing sum panics with the message "attempt to add with overflow".
  • A release build is what cargo build --release makes.
  • The checks are off for speed.
  • The same sum wraps silently.
  • When the compiler can see the overflow coming, it refuses while compiling — no build of either kind ever runs
  • Use cfg!(debug_assertions) to ask, inside the program, which kind of build it is.
  • It is true in debug builds.
  • It is false in release builds.
  • Set overflow-checks = true in the [profile.release] part of Cargo.toml when a release build must keep the checks

In this example, the program first asks which build it is. The output above is the debug run: checks are on. checked_add and wrapping_add answer the same in every build — None, and 0 — because the method families never change. Only plain + changes between builds: it would panic in this build, and it would wrap to 0 in a release build. One program, two honest reports.

Note

The four method families — checked_, wrapping_, saturating_, overflowing_ — mean the same thing in every build.

Only plain operators change their behaviour.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

You ship a release build. A u8 sum inside it passes 255 while the program runs. What happens?