On Casts and Checked-Overflow

This also works with the strict version.

I don't think so...for example, you cannot convert between -128_i8 to 255_u8 with a mask and as, right?

(The other factor here is the semantics of constant expressions, incidentally. const fn may provide a path for allowing weird bit manipulation in constants, but that is not certain and will not be stable. Unclear to me just how important this is.)

That's irrelevant. Anly argument involving "masks" in only relevant when it's about converting from wider types. i8 and u8 have the same width. ((!0u64 >> 16) & 0xFF) as u8 works as expected.

And -128i8 would not covert to 255u8 but to 128u8.

The strict version is the only sane thing that can be done. A special case for types of the same size will lead to undiscoverable errors.

@mahkoh Your arguments are very forceful (and in some cases effective), but I wish you'd take more care in your claims, or at least acknowledge when one of your claims was stronger than intended.

I'm referring in particular to this interchange:

It is not irrelevant. The original quoted text from @nikomatsakis was

Note that @nikomatsakis said "today's semantics".

You directly responded to that with this text:

But your claim did not hold up for iN <-> uN conversions, which was @nikomatsakis's point in his response regarding the strict semantics.

I can understand that you regard iN <-> uN conversions as an inappropriate use of the as operator. (Not everyone agrees with that, but that is why we are attempting to have a dialogue.)

But if someone is attempting to recover today's semantics in tomorrow's version of Rust, it is useful to know by what means they should do so.


Anyway, I personally am considering reordering my preferences. I think that @glaebhoerl has made a good point that there are two distinct mental operators here (bitcast versus coercion), and maybe we should try harder to find a way to separate how they are expressed.

So you’re saying that the focus was not on “mask” but on “all of today’s semantics”. Then tell me how I get today’s semantics of the following by using (at most) a mask.

let x: A = ..
let y: B = x as B;

You don’t, because masks are only useful when you know that you’re casting down.

Since you can’t get “all of today’s semantics”, the sensible interpretation of his post is that he was talking about explicit downcasts.

Can you use concrete integer types in your example? I’m not really clear on what your point is. In any case, what I was trying to say was that if we adopt one of the looser semantics, it is possible to recover the full range of today’s behavior by using a combination of masks and as. I don’t believe this is true for the strict interpretation, precisely because of edge cases like 128_u8 as i8 (and yes, the exact numbers I used before were incorrect). In particular, one would need to use transmute in some cases to recover today’s behavior I believe. Do you disagree?

UPDATE: to clarify, when I wrote “I’m not really clear on what your point is”, I meant: “I’m not clear on what the example is trying to demonstrate.”

Not true. http://doc.rust-lang.org/libc/libc/types/os/arch/posix88/index.html

I did not mean to imply that one can write a single expression that would work for any two sets of integer types, but that given two integer types, one can write a suitable expression.

For some reason I wasn’t able to quote your post without repeatedly reloading the page and for some reason my last reply doesn’t show up properly. This forum software is so terrible (there are many other issues which I won’t list here) compared to even github issues that I won’t continue to post here. Open an RFC once you’ve decided on how you want to clarify the previous RFC.

I presume by this somewhat cryptic reference you are intending to say that sometimes, when writing portable or platform dependent code, one does not know the precise integer types you are working with. In that situation, I agree that one cannot recover the precise semantics of as under any proposal except for making as work just like it works today. I'm not really sure what this argues for exactly, but it's an interesting scenario I was overlooking.

I think this will not work, similar to how it doesn't work to just claim that int is not the standard integer type. Make as do the numerical conversion.

It just doesn't work to say that to_u32().unwrap() is the correct way to convert numerically when you also have the as u32 which works as long as you're in the right domain (int also "worked", it still wasn't ment to be the standard).

To me, this indicates that there’s probably no middle ground that would allow as to be used for both numeric and bitwise casts in all cases, so two separate facilities are needed: one for numeric casts, and one for bitwise casts.

If we're going to use the existing as for one and methods for the other, I think using it for numerical casts is the only consistent approach. All of the arguments that applied to arithmetic seem to apply equally to conversion.

It seems that the vast majority of cases want or can use strict behavior. Looking at your survey of rustc: 2 & 4: You say these want strict. 1: You say this wants width-oriented to catch dropped bits, but since both values are unsigned, strict works just as well. 3: Doesn’t matter 6: Assuming x is unsigned, (x >> 16 | 0xFF) as u8 would work just fine, even with strict, and makes the intent clearer, in my opinion. Which leaves only 5 to use the bitwise methods, and explicitly saying “we’re dealing with this value as bits” in this instance does not seem undesirable.

In my own code, the vast majority of as usages want strict, and the rest are byte selection from unsigned values, where strict works just fine with a mask.

Perhaps we could also have some kind of conversion trait that also allowed as to be used on wrapping types with bitwise behavior.

1 Like

So I'll lay my cards on the table here. I basically agree that strict semantics are what you want most of the time, but I'm still not sure that they should be done through the as keyword. Rehabiliating the as keyword feels like a bit of a lost cause. As we kind of settled with the type ascription RFC, as is supposed to be the "Here be dragons!" conversion operator, so dropping bits doesn't seem totally inappropriate. (*) After all, it also permits a variety of low-level conversions (e.g., *u8 to *u16) that are pretty unsafe. To top it off, it's not the best syntax and nobody can remember the precedence. Moreover, this seems likely to be a more disruptive change than adjusting the arithmetic operators, and the beta deadline is drawing close (practical considerations matter too). So basically I do have the sense that stabilizing some nice methods for doing "checked" conversions (e.g. the options in NumCast) might be just fine, and it's certainly less work that lets us focus on other things over the next week or two.

(*) To be clear, I am aware that the Overflow RFC did specify that as should check. I'm just making the case that we tweak the design. I think this is compatible with the spirit of the RFC, which already divided things up between "math" and "bit" operators. This is just a question of where as falls (as has been said earlier in the thread).

All that said, Felix and I talked today and he said he was going to do some preliminary investigation into implementing this to try and get a better picture of fallout. :slight_smile: I could certainly get behind a stricter as as well, since as I said I do think that strict semantics are useful most of the time.

FWIW, this sentiment rings true for me too.

Well, “here be dragons” in the sense of “here be panics” (as opposed to e.g. implicit widening), I thought. The fact that it also does various pointer-related conversions is a good point though.

It seems like low-level pointer reinterpretation should use a transmute, and not be conflated with integer conversion at all, whether bitwise or numeric. I still think that if we retain as for any kind of integer conversion, it should use strict semantics. It is the simple, easy to grab conversion operator, and I think it should be treated the same as the arithmetic operators for numeric types. (To be clear, I’m suggesting that as be checked (and panic) in debug builds, like the standard arithmetic operators. There should be another facility to get back an option in all builds.)

That said, it almost feels like your post is arguing against as in general. Maybe we should just deprecate it as is and move it’s varied uses to dedicated functionality. As far as I know, pointer reinterpretation can be handled by transmute; sized to unsized is handled by coercion (soon to be facilitated by type ascription); and I think bitwise/wrapping casts should be moved to a separate method, like the wrapping arithmetic operations. If we move numeric, checked-in-debug-builds casts elsewhere, is there anything left? (I might be missing a use or two.)

Maybe numeric casts could be implemented using the unary cast operator I’ve seen discussed elsewhere?