Where is std::num::Saturating? (Going to pre-RFC!)

Because it seems that more people would like to close the gap, I prepared a pre-RFC. Thanks for your comments in advance.


  • Feature Name: missing_integer_wrappers
  • Start Date: 2020-10-10
  • RFC PR:
  • Rust Issue:

Summary

This RFC proposes the addition of Saturating, Overflowing and Checked types in the std::num module.

Motivation

For convenience, the std::num module provides a Wrapping type. This type improves ergonomy of wrapping arithmetics. However, the remaining operation modes (saturating, overflowing and checked) lack similar support. This RFC suggests to close this gap and make this part of the std::num module complete.

According to this discussion on Rust Internals, closing the gap would be appreciated. For instance, checked operations are considered essential for secure implementations of many types of cryptography.

Guide-level explanation

The default semantics of integer arithmetic operations consider an overflow to be an error and panics in debug mode. Built-in integer types provide methods for altenative semantics, which provide better control of the outcome. For instance wrapping_add performs wrapping (modular) addition, or checked_add returns an Option with the None variant when the result overflows.

However, using these methods can be cumbersome and worse readable than arithmetic operators. The Wrapping, Saturating, Overflowing and Checked types allow natural use of arithmetic operators with alternative semantics (as indicated by their names):

let value = (Checked(u32::MAX) + Checked(2)) / Checked(8);
// The computation encountered an overflow, so the overall result is None
assert_eq!(None, value.0);

Reference-level explanation

The Saturating<T> can be almost identical to the existing Wrapping<T> type, it just has to delegate to T::saturating_* methods instead of T::wrapping_*.

The Checked<T> type wraps an Option<T> type:

pub struct Checked<T>(pub Option<T>);

It delegates to T::checked_* methods if none operand of the operation is None. If an operand is None or the delegated method invocation returns None, the result wraps None as well.

The Overflowing<T> type wraps (T, bool):

pub Overflowing<T>(pub T, pub bool);

It delegates to T::overflowing_* methods and just wraps their result, hence the bool component tracks whether an overflow occurred.

Drawbacks

It increases the size of the standard library.

Rationale and alternatives

All the types should provide similar user experience and features. Therefore the described design follows closely the existing implementation and avoids proposals that would affect it.

For the Overflowing type an alternative representation could be:

pub Overflowing<T>(pub Result<T, T>);

The Err variant carries the result that overflew. This approach allows leveraging the feature-rich Result type. The disadvantage is the difference from the return type of overflowing_* methods.

It is worth mentioning some features of the Checked type implemented in the checked crate:

  • It implements the From trait as a convenient alternative to Checked(Some(number)).
  • It implements the Deref and DerefMut traits to return a reference to the wrapped Option.
  • It implements binary operators that take a built-in integer as well.

Implementing the From trait seems to be harmless and it should be probably implemented for the Wrapping type anyway. It would provide the same means for wrapping an integer value, regardless of the actual structure of the wrapping type.

Implementing Deref, DerefMut and the binary operators is more questionable as the boundaries between arithmetic modes become less visible. And while it yet works for the Checked type, because the wrapped Option type can't be confused with a numeric type, it is not viable for the Wrapping and Saturating types.

Prior art

Future possibilities

It might be worth implementing macros for a scoped change of the arithmetic mode:

let value = checked! { a + b * foo(x) }.ok().map(|n| overflowing! { n + 1 })?;

The operands in the expressions are wrapped in the appropriate wrapper type. Here the advantage of using the Result type for the Overflowing wrapper are clearly visible.

2 Likes