diff --git a/text/0000-saturating-and-checking-integer-wrapper-types.md b/text/0000-saturating-and-checking-integer-wrapper-types.md new file mode 100644 index 00000000000..2ba0207a050 --- /dev/null +++ b/text/0000-saturating-and-checking-integer-wrapper-types.md @@ -0,0 +1,188 @@ +- Feature Name: saturating-and-checking-integer-wrapper-types +- Start Date: 2016-03-07 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Implement two wrapper-types in `std::num` which provide two different types of behavior on overflow: +saturating arithmetic and always checked arithmetic with the latter signalling a thread panic on +overflow. + +# Motivation +[motivation]: #motivation + +* Currently the only wrapper type, `Wrapping`, provides wrapping semantics on the basic operators + but saturating or checked semantics are obtained through using methods directly on the primitive + type. `Saturating` and `Checked` types would improve the ergonomics of using saturating and + checked arithmetic. +* Firefox media team wants to have a `Checked` type which will panic on overflow in release mode + which they can use in non-performance critical code. Currently writing checked code which causes + a thread panic is far from ergonomic. +* `Saturating` would provide defined saturating behavior for division, remainder and negation + as well as left- and right-shift operations. Currently only addition, subtraction and + multiplication are implemented for the primitive types in the form of the `saturating_*` methods. +* Improved performance can potentially be obtained for some of the operations by using intrinsics + which would not be possible to do in a stable crate at the moment. + +# Detailed design +[design]: #detailed-design + +This proposal suggests two additional types alongside the intentionally wrapping wrapper-type +`Wrapping`: `Saturating` and `Checked`. + +The two types will implement the same traits as `Wrapping`. Below `W` is the wrapper-type +(`Wrapping`, `Saturating` or `Checked`) and `T` is the wrapped integer primitive: + +```rust +#[derive(Debug, Default, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] +pub struct W(pub T); + +impl Add> for W { type Output = W; ... } +impl Sub> for W { type Output = W; ... } +impl Mul> for W { type Output = W; ... } +impl Div> for W { type Output = W; ... } +impl Rem> for W { type Output = W; ... } + +// Only for signed `T` +impl Neg for W { type Output = W; ... } + +impl Not for W { type Output = W; ... } +impl BitXor> for W { type Output = W; ... } +impl BitOr> for W { type Output = W; ... } +impl BitAnd> for W { type Output = W; ... } + +impl Shl for W { type Output = W; ... } +impl Shr for W { type Output = W; ... } + +impl AddAssign> for W { ... } +impl SubAssign> for W { ... } +impl MulAssign> for W { ... } +impl DivAssign> for W { ... } +impl RemAssign> for W { ... } +impl ShlAssign for W { ... } +impl ShrAssign for W { ... } +impl BitXorAssign> for W { ... } +impl BitOrAssign> for W { ... } +impl BitAndAssign> for W { ... } +``` + +The `*Assign` trait implementations will perform the requested operation with the same semantics +as the base implementation (eg. `AddAssign` will perform an addition using `Add` and assign the +result to the left hand side). + +## `Checked` +[checked]: #checked + +The `Checked` wrapper type should provide checked operations for `+`, `-`, `*`, unary `-`, `/`, +`%`, `<<` and `>>` which panic in the case of overflow like the primitive types do in debug-mode. +Unlike the operations on the primitive types this checked arithmetic should be kept in release mode, +preserving the semantics of panic on overflow. + +Bitwise operations will be forwarded to the wrapped type. + +## `Saturating` +[saturating]: #saturating + +```rust +assert_eq!(Saturating(255u8) + Saturating(1), Saturating(255u8)); +assert_eq!(Saturating(128u8) << 1, Saturating(255u8)); +``` + +* The operators `+`, `-` and `*` saturate to `MAX` and `MIN` values for both signed and unsigned + integers. + +* `/` and `%` cannot overflow on unsigned integers. For signed integers `MIN / -1` and `MIN % -1` + can overflow since signed types follow two's complement: `-1 * MIN = MAX + 1`. + + The proposed results are `MIN / -1 = MAX` and `MIN % -1 = 0`. + +* Unary `-` (negation) of `MIN` in signed integers should saturate to `MAX` in the case of `-MIN`. + +* Bitshift operators (`<<` and `>>`) saturate to `MAX` and `MIN` in the case of overflow of + unsigned integers. For signed non-zero positive integers `<<` saturate to `MAX` and `>>` + saturate to `0`. For signed non-zero negative integers `<<` saturate to `MIN` and `>>` saturate + to `-1`. Zero cannot saturate. + +* Bitwise operators operate directly on the wrapped value, just as `Wrapping`. + +# Drawbacks +[drawbacks]: #drawbacks + +* Two additional wrapper-types to maintain in the standard library +* The original numeric values still need to be lifted to the desired wrapping type (see + [ergonomics-of-wrapping-operations](https://internals.rust-lang.org/t/ergonomics-of-wrapping-operations/1756) + for a discussion about the ergonomics of `Wrapping` and `wrapping_*` methods of primitive + integer types). This is more of a question about the semantics of `T + W` and `W + T` + which is not defined at the moment and not a part of this RFC. +* Interest for alternative behavior on overflow might not warrant additional wrapper-types in + stdlib. + +# Alternatives +[alternatives]: #alternatives + +## Do nothing + +Instead of `Checked` and `Saturating`, use the `checked_*` and `saturating_*` operations +which are provided as inherent methods on the primitive integer types. + +This can result in excessively verbose code for calculations, especially in the case of +checked arithmetic: + +```rust +let a = 5; +let b = 4; +a.checked_add(b).unwrap().checked_mul(3).unwrap() +a.checked_add(b.checked_mul(3).unwrap()).unwrap() +// vs +let a = Checked(5); +let b = Checked(4); +(a + b) * Checked(3) +a + b * Checked(3) + +let a = 5; +let b = 4; +a.saturating_add(b).saturating_mul(3) +a.saturating_add(b.saturating_mul(3)) +// vs +let a = Saturating(5); +let b = Saturating(4); +(a + b) * Saturating(3) +a + b * Saturating(3) +``` + +## Implement `Checked` and `Saturating` in an external library. + +Additional dependency for a somewhat basic feature which already partially exists in stdlib +(through the `Wrapping` type). + +Performance can also be a concern since intrinsics cannot be used for the implementation. + +## Implement checked and saturating arithmetic as separate operators + +This has been proposed for wrapping operators and the conclusion was: + +> Reasons this was not pursued: New, strange operators would pose an entrance barrier to the +> language. The use cases for wraparound semantics are not common enough to warrant having a +> separate set of symbolic operators. + +See: [RFC #0560](https://github.com/rust-lang/rfcs/blob/master/text/0560-integer-overflow.md) + +## Scoped attributes + +This was also [proposed for wrapping operators](https://github.com/rust-lang/rfcs/pull/146), +but failed when the proposal for [checked arithmetic in debug mode](https://github.com/rust-lang/rfcs/pull/560) +was proposed. The latter also introduced `Wrapping`. + +# Unresolved questions +[unresolved]: #unresolved-questions + +* The saturation behavior of `>>` on signed integers. Currently this is specified to saturate to + `-1` since that is the right-shift behavior according to two's complement (ie. keep the sign bit). + +* The name of `Checked`. + + Currently the checked arithmetic provided by the primitive types (through `checked_*` methods) + do not panic on overflow. Naming the type `Checked` when it instead panics on overflow could + confuse people. Another option mentioned was `Strict`.