Add Wrapping/Saturating::from_mut reference conversions #327
Labels
ACP-accepted
API Change Proposal is accepted (seconded with no objections)
api-change-proposal
A proposal to add or alter unstable APIs in the standard libraries
T-libs-api
Proposal
Problem statement
The Wrapping and Saturating types have stable layout, defined as equivalent to the inner public numeric type. This means that it's easy to convert owned values of
T
andWrapping<T>
. However, conversion of references (&
/&mut
) is also sound, since the wrapper imposes no extra invariants on its contents, but it's impossible to do safely. The stable solution is to domem::transmute::<&mut Wrapping<T>, &mut T>(ref_mut)
, which is unsafe and a mouthful to write.Motivating examples or use cases
The primary motivation is to be able to use op-assign operators with wrapping/saturating semantics.
In my experience using
&Wrapping<T>
/&mut Wrapping<T>
in API is extremely uncommon (personally I struggle to think of any examples beyond the implementations of general traits, likePartialEq
orAddAssign
, for those types). In my view, this is due to a combination of issues:Wrapping/Saturating
, instead working with the relevantwrapping_*/saturating_*
methods on the integer types themselves. That clearly signifies the intent at use site, avoids conversions between the wrapped and raw numeric types, and allows easily to intermix different types of operations (ordinary, overflowing, wrapping, saturating, checked) on the same data, without any extra conversions.For this reason I think that conversions
&Wrapping<T> <=> &T
(similarly for&mut
andSaturating
) are typically unnecessary. A primary counterexample is op-assign operators (+=
,-=
etc). Currently it is impossible to easily use them to implement wrapping/saturating operations on integer types. The wrapping analogue ofa += b;
currently looks likeIt's more verbose, since it requires to repeat the expression
a
twice (note that it could be some complex place expression, like*foo.bar[5].baz()
). It's also more error-prone, since the place expression may contain operators and function calls (e.g. indexing and.baz()
in the previous example), and there is no guarantee that those operations are cheap or idempotent for the relevant types.With this proposal, the wrapping/saturating op-assign operators could be implemented as simply as
without introducing any new traits, or code duplication.
The above example is particularly common when translating C/C++ code into Rust, since all operations on unsigned integers in those languages have wrapping semantics.
One of the benefits of the proposed approach is that the use of wrapping operations on ordinary integer types is obvious at use site (due to the presence of
Wrapping::from_mut
), and it is easy to refactor this code into a non-wrapping arithmetic operation if need be.Solution sketch
Add the following functions:
As seen above, I also propose to add the inverse conversions
&mut Wrapping<T>, &mut Saturating<T> => &mut T
. We could add it as a separate method on the types, but the signature and semantics of those methods fit the existingAsMut
trait perfectly, so imho it's best to use it. The API to remove the wrapper is mostly included for completeness. If one works with wrapping types instead of raw integers, it allows to easily apply non-wrapping ordinary (potentially panicking) operations to the contents. I don't think it's a common need, foremost because using the wrappers is itself not super common, but it's nice to have the symmetry.Note that I do not propose to add the matching API for conversions
&T <=> &Wrapping<T>
. We could add them with similar implementation and reasoning, using theAsRef
trait for the inverse conversion. However, I don't know of a good use case for those APIs, since the operator traits don't use them, and thePartialOrd
,PartialEq
etc traits have the same behaviour for both the wrappers and the wrapped types. Since we can always add those methods later if need be, I omit them from this proposal, but I'm open to add them if anyone has a use case where by-value operations on dereferenced expressions are insufficient.Alternatives
The unique inner field of the wrappers is public, so one can always implement the required conversions in user code, possibly on extension traits. However, the implementation requires
unsafe
code, and it's good to provide safe interfaces instd
where possible.There are also proposals [rfc] for language-level conversions between the references to wrappers and their contents. Afaik no such RFC has been accepted thus far. One issue is the precise stability and soundness guarantees for such conversion. Even if such a feature is ever stabilized, the proposed methods are still as usable, and the feature can be used to safely implement the relevant methods.
Given the already existing similar methods on atomic types(unstable), UnsafeCell(unstable), arrays and slices, there is already prior art for this kind of conversions, which makes them natural to expect for the users, and thus more discoverable.
&mut T -> &mut Self
, inverse toAsMut
. For example,Links and related work
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. 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:
The text was updated successfully, but these errors were encountered: