The “most natural explanation” will depend on the reader and their use case. Good documentation should explain both perspectives in parallel; it's not an accident that both modular arithmetic and bit reinterpretation are reasonable descriptions, because that's one of the reasons computer architectures use two's complement representation.
Given the modular arithmetic perspective, the as operator is very surprising and error prone, because all other arithmetic operations do not perform modular arithmetic on overflow (with the exception of wrapping_* functions). It is very strange that this one operator treats u32 as a "wrapping" type while the rest of built-in operators don't.
I entirely agree that as is hazardous (and should be split into separate traits/operators that make it clear which action it's performing). I only meant to claim that for the specific case of signed ↔ unsigned conversion, wherever it is done, its semantics are best documented using both perspectives rather than choosing one.
One question I'd like to ask (for the pre pre pre rfc that is this thread) is if it's okay for certain as casts to be broken into multiple operations. For instance -7i8 as u32 could be replaced with u32::bit_reinterpret_mod(i32::from(-7i8)) if no direct operation is added.
Anything with mismatched signedness and increasing size is interesting because the ordering of the parts is important. That gets to the same thing as
since (-7_i8) as u8 as u32 and (-7_i8) as i32 as u32 do different things.
I think two steps might be ok, but only if it means typing out one type name, not one type name for each part.
For example, i32::from(-7_i8).reinterpret_unsigned() might be ok, with that hypothetical method knowing the corresponding unsigned type the same way that unsigned_abs does.
Yes indeed, that is exactly why this thread exists. If bitcasts were all there is to it, nothing is wrong with as, but something is wrong to it because there is more to it than just bitcasting.
That's a ridiculous amount of boilerplate code for something which is essentially a simple and (relatively) well-known operation. I'm all for providing finer-grained functions with a clear name, but there is just no way I would use such long-winded constructs instead of as-casts in cast-heavy code.
If I'm writing FFI, then very often the only cast semantics that I want is "what C does", at least until I have ergonomic safe wrappers. If I'm writing computationally heavy code which already heavily relies on wrapping arithmetics and integer casts, the last thing I want is to obscure the computation flow with long-winded names. as-casts work perfectly in both cases: explicit enough to make the exact operations clear, yet concise enough to not get in the way too much. A replacement should be just as concise if I were ever to use it in those cases, and even from-conversions are straining it.
Just to make it clear: I don't care if casts between pointers or between floats and integers are deprecated. Both of those are rare enough and complex enough to warrant well-named functions. int/float casts in particular would benefit strongly from more fine-grained conversions. However, integer casts are bread and butter in many algorithms. Widening, narrowing and changing the signedness are very common and must be ergonomic.
Disallowing -7i8 as u32, but allowing -7i8 as i32 as u32 could be a better alternative. as would be allowed either between iX and uX where X is the same number of bits for both integer types, and between iX/iY or between uX/uY for cast of same-signness integers (and maybe only for non-lossy extensions). as could be used either to reinterpret the bits, or to change the number of bit of an integer, but not both at the same time.
// Type of `x` is inferred as `u32` from context.
let x = i32::from(-7i8).as();
Hopefully that isn't too bad. If a particular section of code has a lot of mixed-sign mixed-width casts one can always make a short local function.
In typical Rust fashion I'd expect AsInto and AsFrom traits for bit/reinterpret/mod casting (auto implemented for Into/From pairs?). And perhaps AsFrom/AsInto could remain part of the language powering operator as, as long as we still get warnings/errors.
I'm hoping it's even simpler than that; I'd like all these operations to be postfix. Going from i8 to i32 is just into, but we need a way to specify the type sometimes. (We could make .into::<i32>() just work, if we're willing to, or add some other mechanism that's no more verbose than that.) Going from i32 to u32 should be a single safe infallible method call.
This is quite a good idea, I think at least the compiler should emit a warning if the integer casting is performed between diff-width and diff-sign integers