Description
Proposal
Problem statement
Rust provides convenient access to several different ways of doing arithmetic:
a + b
: shouldn't overflow but this is not checked in all configurationsa.wrapping_add(b)
: overflow is explicitly finea.saturating_add(b)
: saturate the result on overflow
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):
- We think this problem seems worth solving, and the standard library might be the right place to solve it.
- We think that this probably doesn't belong in the standard library.
Second, if there's a concrete solution:
- We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
- We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.