Why minus operation on usize numbers can't be negative and what's Rust idiomatic way to handle this?

Viewed 1190

s and t are vectors. If we do this:

t.len() - s.len()

It may result in attempt to subtract with overflow error if the subtraction result is negative (since both types' are usize).

However, it may cause a tricky bug if we are not aware that both types are usize and we expect the result can be a negative number (I encountered myself and only catch it during runtime on edge cases). To fix the bug, I converted them to:

t.len() as i32 - s.len() as i32

My questions are:

  • Why Rust doesn't convert usize substraction to a signed type?
  • What's Rust idiomatic way to catching this type of bugs early during compile time?
2 Answers

To fix the bug, I converted them to:

t.len() as i32 - s.len() as i32

Depending on platform, usize can be 64b, isize exists for the purpose of diffing usizes though it still has only half the positive range of a usize, casting to i128 would provide more certainty (at least until we get platforms with 128b usize) but then it might be very inefficient on e.g. 32b platforms as they may not have hardware support for 128b operations and they are implemented in software instead.

Why Rust doesn't convert usize substraction to a signed type?

Because arithmetic operations on numeric types are bulk-implemented with the same input and output using a macro.

And the option you suggest would just have its own issues e.g. usize::MAX - 1 would return garbage.

What's Rust idiomatic way to catching this type of bugs early during compile time?

Numeric types have methods for explicit overflow behaviour: failing (checked_*), wrapping (wrapping_*) or saturating (saturating_*). There's a wrapper type for wrapped operations as that's very common in some contexts (e.g. cryptography) but I don't think there's one for checked operations.

There is no really idiomatic way to handle this, because it's a case-by-case issue and for once this failibility was judged too inconvenient and inhabitual to surface by default.

I don't know the idiomatic way but I believe usize::checked_sub or usize::overflowing_sub may be the good solution.

So finally it may looks like:

match t.len().checked_sub(s.len()) {
    None => println!("Error"),
    Some(diff) => println!("Difference is {}", diff),
}
Related