You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Over the past few years, Rust has gained explicit (stable and unstable) methods to cast between integer types and communicate intent of the conversion, without the overloaded as operator being present in user code.
However, there's no way to convert between signed/unsigned integers and clamp to the target type. User-written clamp implementations are required for this kind of conversion but also introduce more opportunities for bugs.
Motivating examples or use cases
One place where this is often re-implemented is in image/RGB pixel code. Arithmetic is done in i16 or i32 and then clamped to u8.
// i32 -> u8let result = value.max(u8::MINasi32).min(u8::MAXasi32)asu8;let result = value.clamp(u8::MINasi32, u8::MAXasi32)asu8;
Mapping from u8 to i8 currently can look like this, but the intent is obscured.
// u8 -> i8let result = value.min(i8::MAX.cast_unsigned()).cast_signed();
as casting from floats to integers today is already a lossy, saturating conversion. Clamping elements out of the target range to the target minimum or maximum can be a desired way of handling out-of-range values.
// Out of range float-to-int conversions saturate todayassert_eq!(-1.0_f32asu8,0u8);assert_eq!(128.0_f64asi8,127i8);
Saturating integer conversion would cover point 5 from the problem statement of #204, which is the last major conversion behavior missing from the standard library for integers.
keep numerical value and saturate if out of range
Solution sketch
The API is inspired by the already existing cast_signed/cast_unsigned on integers and designed to compose with the recently accepted integer truncate/extend methods from #204.
These functions are meant to bridge the gap between signed and unsigned equal-bit-width integers. From, extend<Target>, or saturating_truncate<Target> can then be used to reach the destination type.
impl{uN,usize}{/// Converts `self` to the signed integer of the same size, clamping/// `self` to the signed integer's maximum value if necessary.pubconstfnsaturating_cast_signed(self) -> iN{ifself < <$SignedT>::MAXas $ActualT {selfas $SignedT
}else{
<$SignedT>::MAX}}}impl{iN,isize}{/// Converts `self` to the unsigned integer of the same size, clamping/// `self` to `0` if necessary.pubconstfnsaturating_cast_unsigned(self) -> uN{ifself < 0{0}else{selfas $UnsignedT
}}}
Thus, the motivating examples above can be rewritten as follows.
// i32 -> u8let result = value.saturating_cast_unsigned().saturating_truncate::<u8>();// u8 -> i8let result = value.saturating_cast_signed();
Alternatives
Possible extensions to this proposal
Further casting functions that may be reasonable to add are strict_cast_* and checked_cast_*. These can be handled by TryInto/TryFrom traits but may be convenient to spell with explicit methods.
strict_cast_* - A cast that panics for integers outside of the range for the target type. This would be equivalent to value.try_into().unwrap() and follows in the spirit of the strict_ops added in Add operations for easy arithmetic that panics on overflow #270 which provide an alternative to wrapping ops.
checked_cast_* - Option returning cast that returns None when the source integer is out of range for the target, like value.try_into().ok().
In the comments for this proposal, unchecked and overflowing casts were suggested but I do not have any real-world use case for those.
Do nothing. Users can still write manual conversions or use third-party crates.
Implement a more powerful cast_saturating which could freely cast between any of the fixed-width integer types. This has the drawbacks of covering many lossless From conversions (unsigned to larger signed and unsigned types) and ignoring the truncate/extend API. The functionality is useful but a bit too magical to live in std.
Pursue trait implementations like the TryLossy or other RFCs.
Links and related work
Other languages
P0543R3: Saturation arithmetic - accepted C++26 paper for saturating add, sub, multiplication, division, and casting for signed and unsigned integers
P4052R0: Renaming saturation arithmetic functions - accepted C++26 paper for renaming the saturating operations to match Rust's saturating_* convention and to consider that scheme (overflowing_, widening_, wrapping_, etc.) for future proposals.
Integer extension and truncation methods - #204 strict_* arithmetic ops that panic on overflow - #270 cast_signed/cast_unsigned - #359
Crates
These crates offer solutions like Alternative 2 listed above. Both crates allow users to implement the saturating behavior for their own types so they still have use cases even if this proposal is accepted.
Provides saturating casts between integers as well as floats and integers. However, this crate chose to panic when a float is NAN instead of the saturating behavior eventually chosen by the language.
use az::SaturatingAs;assert_eq!((-1).saturating_as::<u32>(),0);assert_eq!((17.0 + 256.0).saturating_as::<u8>(),255);
Disclaimer, I am the author of this crate (and I wrote it without knowing about az at the time).
It works similarly by using a marker trait to implement the behavior of casting between elements, but only works between integers.
use saturating_cast::SaturatingCast;let x:i32 = 1024;let y:u8 = 10;let z = x.saturating_cast::<u8>() - y;assert_eq!(245, z);
Proposal
Problem statement
Over the past few years, Rust has gained explicit (stable and unstable) methods to cast between integer types and communicate intent of the conversion, without the overloaded
asoperator being present in user code.However, there's no way to convert between signed/unsigned integers and clamp to the target type. User-written clamp implementations are required for this kind of conversion but also introduce more opportunities for bugs.
Motivating examples or use cases
One place where this is often re-implemented is in image/RGB pixel code. Arithmetic is done in
i16ori32and then clamped tou8.Mapping from
u8toi8currently can look like this, but the intent is obscured.ascasting from floats to integers today is already a lossy, saturating conversion. Clamping elements out of the target range to the target minimum or maximum can be a desired way of handling out-of-range values.Saturating integer conversion would cover point 5 from the problem statement of #204, which is the last major conversion behavior missing from the standard library for integers.
Solution sketch
The API is inspired by the already existing
cast_signed/cast_unsignedon integers and designed to compose with the recently accepted integer truncate/extend methods from #204.These functions are meant to bridge the gap between signed and unsigned equal-bit-width integers.
From,extend<Target>, orsaturating_truncate<Target>can then be used to reach the destination type.Thus, the motivating examples above can be rewritten as follows.
Alternatives
Possible extensions to this proposal
Further casting functions that may be reasonable to add are
strict_cast_*andchecked_cast_*. These can be handled byTryInto/TryFromtraits but may be convenient to spell with explicit methods.strict_cast_*- A cast that panics for integers outside of the range for the target type. This would be equivalent tovalue.try_into().unwrap()and follows in the spirit of thestrict_opsadded in Add operations for easy arithmetic that panics on overflow #270 which provide an alternative to wrapping ops.checked_cast_*-Optionreturning cast that returnsNonewhen the source integer is out of range for the target, likevalue.try_into().ok().In the comments for this proposal,
uncheckedandoverflowingcasts were suggested but I do not have any real-world use case for those.cast_saturatingwhich could freely cast between any of the fixed-width integer types. This has the drawbacks of covering many losslessFromconversions (unsigned to larger signed and unsigned types) and ignoring the truncate/extend API. The functionality is useful but a bit too magical to live instd.TryLossyor other RFCs.Links and related work
Other languages
saturating_*convention and to consider that scheme (overflowing_,widening_,wrapping_, etc.) for future proposals.add_satbecomessaturating_add,saturate_castbecomessaturating_cast.RFCs
Traits for lossy conversions - rust-lang/rfcs#3415
ACPs
Integer extension and truncation methods - #204
strict_*arithmetic ops that panic on overflow - #270cast_signed/cast_unsigned- #359Crates
These crates offer solutions like Alternative 2 listed above. Both crates allow users to implement the saturating behavior for their own types so they still have use cases even if this proposal is accepted.
az-saturating_asProvides saturating casts between integers as well as floats and integers. However, this crate chose to panic when a float is NAN instead of the saturating behavior eventually chosen by the language.
saturating_cast-saturating_castDisclaimer, I am the author of this crate (and I wrote it without knowing about
azat the time).It works similarly by using a marker trait to implement the behavior of casting between elements, but only works between integers.