I am a little confused about casting between signed and unsigned integers in Rust.
Here is a scenario I have in mind: I have an unsigned constant CONSTANT (less than 32 bits required to represent) and I have to pass it to some functions that expect an i64 and some that expect a u64.
There seem to be two ways of implementing this in Rust:
const CONSTANT: u64 = 160;
function_that_requires_i64(CONSTANT as i64);
Intuitively, I tend to prefer the first option, because it seems more explicit. My intuition regarding the second version is that I might get a problem when the constant is actually something larger than what fits into an i64 (maybe u64::MAX).
Can someone explain what the (internal) difference between the two versions is? Which one do you prefer, and why? Can you think of (edge) cases where they have different behaviour?
The From::from conversion accepts only what are considered to be infallible conversions while as accepts conversions that, well, don't quite fail but truncate or wrap the value. So if you want to guarantee that the value that is being converted is not truncated or wrapped around, use From. The clippy lint clippy::checked_conversions can help enforce the use of From and otherwise TryFrom methods over as.
The use of From over as is more "robust to change" elsewhere in the code. You would not be able to change the definition of CONSTANT to a type that can not be converted to an u64 and i64 without risking truncation or wrapping. For this reason alone, and without further context, I would generally recommend to use From.
I'd also prefer the first option and so does pedantic clippy. This lint catches casts that could possibly wrap the value when casting from an unsigned to a signed integer type with the same size. Wrapping would happen in your second case if your constant is bigger than i64::MAX, which would result in funny integers like u64::MAX as i64 == -1.
The as operator will silently convert values modulo 264 (so u64::MAX becomes -1) which is very rarely what you want, and for that reason people have even been talking about deprecating this operator in this context.
try_into().unwrap() will panic at runtime if the number is too large, which is not optimal (you'd prefer to fail at compile time), but better than silently getting a different number.