DEV Community

Rabeh_Sys
Rabeh_Sys

Posted on

Even a Rust Progress Bar Can Become a Reliability Problem

While building CommitGuard, I realized something interesting:


even a simple CLI progress percentage can become dangerous under very large workloads.

At first I had the classic calculation:

let percentage =
(current_items * 100) / total_items;

Looks harmless.

And for many projects, it probably is.

But CommitGuard is designed for huge repositories, fuzzing workloads, and long-running scans. That forced me to think more carefully about integer arithmetic and long-term reliability.

The Overflow Problem

The dangerous part is this:

current_items * 100

If "current_items" becomes large enough, multiplication overflows before division even happens.

In Rust:

  • Debug builds panic.
  • Release builds wrap around.

That means your progress percentage may silently become incorrect while the program keeps running.

The code compiles perfectly.
But correctness is already gone.

Checked Arithmetic

In low-level and defensive systems, I strongly prefer explicit arithmetic checks.

Something like this:

let scaled =
current_items.checked_mul(100);

match scaled {
Some(value) => {
let percentage =
value / total_items;
}
None => {
// overflow detected
}
}

Why?

Because "checked_*" makes overflow visible instead of hiding it.

For long-term systems, silent corruption is usually worse than explicit failure.

Saturating Arithmetic

Another possible approach is saturating arithmetic:

let percentage =
current_items
.saturating_mul(100)
/ total_items;

This avoids crashes and prevents wraparound behavior.

However, saturation may still produce incorrect logical results once values hit the numeric limit.

So while it is safer than wrapping arithmetic, it is not always mathematically accurate.

Widening Arithmetic

For the progress calculation itself, I ended up widening the arithmetic:

let percentage = if total_items == 0 {
100
} else {
(
(current_items as u128 * 100)
/ total_items as u128
) as usize
};

Using "u128" here makes overflow practically impossible for realistic workloads while keeping the implementation allocation-free and extremely lightweight.

This was a good balance between correctness, simplicity, and hot-path performance.

Why I Avoided Heavy Progress Libraries

While profiling CommitGuard, I noticed many progress implementations rely on:

  • background threads
  • shared state
  • "Arc>"
  • heap allocations
  • constant terminal writes

For normal CLI tools, this is usually fine.

But for high-frequency scanning loops, tiny overheads accumulate very quickly.

Especially when progress updates happen millions of times.

So I kept the implementation intentionally simple:

  • no heap allocations
  • no background workers
  • no synchronization overhead
  • no hidden allocations in the hot path

I also throttle terminal updates to around every 250ms instead of constantly redrawing the screen.

That alone significantly reduces terminal I/O overhead.

Small Math Becomes Systems Engineering

One thing Rust keeps teaching me is this:

there is no such thing as “just simple code” in long-term systems.

A tiny arithmetic assumption can eventually become:

  • a panic
  • corrupted state
  • invalid metrics
  • undefined behavior
  • performance degradation

Especially under fuzzing or massive workloads.

The code may compile.
The tests may pass.
But systems programming starts where edge cases begin.
And honestly, that is one of the reasons I enjoy Rust so much.

Top comments (0)