rust-lang / libs-team

The home of the library team
Apache License 2.0
115 stars 18 forks source link

Add operations for easy arithmetic that panics on overflow #270

Closed RalfJung closed 11 months ago

RalfJung commented 11 months ago

Proposal

Problem statement

Rust provides convenient access to several different ways of doing arithmetic:

There are also a.checked_add(b) and a.overflowing_add(b) but those are significantly different: they are not of the shape fn(T< T) -> T, so one cannot easily use them in larger arithmetic expressions.

What is missing is the natural dual to a.wrapping_add(b): arithmetic that is guaranteed to panic on overflow. One can write a.checked_add(b).unwrap(), but the extra unwrap makes this very verbose, in particular for larger operations:

a
  .checked_add(b.checked_mul(2).unwrap())
  .unwrap()
  .checked_add(c)
  .unwrap()
  .checked_mul(3)
  .unwrap()

or

a
  .checked_add(b.checked_mul(2).unwrap())
  .and_then(|x| x.checked_add(c))
  .and_then(|x| x.checked_mul(3))
  .unwrap()

Motivating examples or use cases

There are parts of Miri where I want all arithmetic to be definitely overflow-checked, and currently we have code like in the example above, and it is an eyesore.

I find it surprising that we make it easier to write "guaranteed to wrap" arithmetic than "guaranteed to panic on overflow" arithmetic. That seems to be the exact opposite of where we actually want to steer people towards! (We have a flag to globally make all regular arithmetic panic-on-overflow, of course. But that's global for the entire binary [not even just a crate!] and e.g. not an option for Miri for performance reasons.)

Solution sketch

I propose we give "panic on overflow"-style arithmetic the same status as "wrap on overflow"-style arithmetic, and provide a nice operation with the type fn(T, T) -> T for that. I propose we call this strict_add, but the name is obviously open to bikeshedding. This applies not just to add but to all arithmetic operations that have a checked_ variant.

a
  .strict_add(b.strict_mul(2))
  .strict_add(c)
  .strict_mul(3)

We could furthermore also add a Strict<i32> type that is similar to Wrapping<i32> and Saturating<i32>, but uses guaranteed overflow-checked arithmetic. But this is an optional extension of the ACP; having the methods as a first step would already be useful.

((Strict(a) + (Strict(b) * 2) + c) * 3).0

(This assumes that we have impl Add<i32> for Strict<i32> etc, which we currently do not seem to have for Wrapping, to my surprise.)

Alternatives

Other possible names: panicking_add, and a Panicking<i32> type. Or maybe something like unwrap_add or assert_add?

An alternative to Strict<T> as a newtype around T would be

enum Checked<T> {
    Value(T),
    Overflow,
}

and then having operator overloading for Checked<i32> etc. That wouldn't provide strict_* methods but (with enough operator overloading) one could write

((Checked::new(a) + (Checked::new(b) * 2) + c) * 3).unwrap()

We could do nothing, and people have to keep writing tons of unwrap everywhere. We could leave it to a library crate to provide this feature.

Links and related work

What happens now?

This issue is part of the libs-api team API change proposal process. Once this issue is filed the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

Second, if there's a concrete solution:

BurntSushi commented 11 months ago

Seconded.