Description
Proposal
Problem statement
Casting using the as
operator has some issues, in that it does not necessarily reflect the intent of the cast. Was the cast meant to be lossy or not? Was it meant to preserve the size? Constness? Signedness?
Some of the motivations for pointer constness casts raised at https://internals.rust-lang.org/t/casting-constness-can-be-risky-heres-a-simple-fix/15933 would apply here too. When refactoring, the sizes of types may change, and silently introduce bugs due to casts not being updated.
Clippy has the cast_possible_wrap
and cast_sign_loss
lints, which tag sign-changing casts, but since there is no alternative to using as
, it cannot propose a fix.
Motivating examples or use cases
Rust has been slowly introducing alternatives to direct casts, to more clearly signal intent, catch errors, and allow casting only one aspect of a type instead of everything in one go. From
is used when the cast is lossless, TryFrom
for checked casts, ptr::cast_const
and ptr::cast_mut
change the mutability and nothing else, ptr::cast
changes pointed type and nothing else.
Solution sketch
Following the example of ptr::cast_const
and ptr::cast_mut
, I propose adding two new methods to integer types, for all X including size
:
uX::cast_signed() -> iX
iX::cast_unsigned() -> uX
These do the same as a regular as
cast, but crucially they only cast to another integer of the same size and opposite signedness. They don't ever change the size, so i32
→ u64
and u64
→ i32
are not implemented.
Alternatives
Other names for the two proposed methods would be possible, such as:
reinterpret_*
, following the example of C++.transmute_*
, since this is really a kind of transmute.cast_sign
for both methods. This has the downside of not conveying which direction you're casting in, which the pointer constness methods do indicate.iX::from_bits
andiX::to_bits
, following the example of floats. The methods would only be present on signed integers in this case.
It would also be possible to implement this as a trait, like the explicit_cast
crate does, but since it's a closed class of types, that seems overkill. There have also been proposals to include this under a more general "wrapping cast" or "lossy cast" function/trait of some sort, but I feel that a i32
→ u32
cast isn't in the same class of casts as i32
-> i16
, since the former is fully reversible and the latter is actually lossy.
Links and related work
- Community Discord discussion: https://discord.com/channels/273534239310479360/957720175619215380/1221934905844432996
- Internals: Let's deprecate
as
for lossy numeric casts - Internals: Pre-RFC: Add explicitly-named numeric conversion APIs
- Reddit: Generically converting integers between signed and unsigne
explicit_cast
crate: https://docs.rs/explicit_cast/latest/explicit_cast/trait.SignCast.html- RFC: Conversions: FromLossy and TryFromLossy traits