Closed bbaldino closed 1 year ago
@chrysn @Kijewski does this seem legitimate to you? If so I'll clean up some local changes I have and do a PR.
The current shl_assign code uses shl(), which already masks the result of the left shift. Can you verify that this is still an issue with the latest git version?
On master? Still looks like I pasted above from what I can tell: https://github.com/rust-ux/uX/blob/10819303ffeb8093556bbdd81fca94fb77b58237/src/lib.rs
My apologies, I was on the pr-niche
branch (which would, consequently, fix this issue).
The fix you propose looks good; could you provide a PR?
(If we go through with #49, which I'm generally positive about, it'll be after a breaking version bump, and this would be a good fix to have in 0.1.6).
A more correct version (consistent with documented behavior) would be to panic when in debug mode, so feel free to address that in the PR as well, but fixing the correctness issue has high enough priority over the debug panic that a fix for it alone would be good just as well.
Ah that's interesting, is that a stdlib doc for shlassign? Or something here? I was thinking it'd just silently drop, just like you can shift-assign a top bit "out" of u8, u16, etc.
Bits are silently shifted out all the time, but shifting by more than the bit width is an error in development mode according to https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow
So as part of digging into this a bit, I noticed it's not straightforward to test, because PartialEq
, for example, does masking. Looking around, it looks like many trait implementations do the masking there (on "read"), as opposed to doing the mask on modification (on "write").
I suspect that some trait implementations may be missing the mask call--unfortunately I don't remember which one I was trying to use when I stumbled across this before. I took a look through, though, and did find some:
Display
doesn't mask, and will show an "unmasked" value (e.g. a u9
could show a value of 512
when printed). Same for UpperHex
, LowerHex
, Octal
, Binary
. I'd think there's value in enforcing that a uX type is always in a "valid" state: i.e. masking should be done on write, not read, but I assume this was done intentionally, so perhaps there was good reason. So perhaps the fix should just to change the display traits mentioned above to mask.
I don't really remember the complete picture here. But I assume the inconsistent masking on read/write is due to changing the strategy somewhere along the way.
I guess the two valid options are:
I'm not sure which one is the correct one to use. Maybe the greatest virtue of (2) is that a buggy implementation will be observed when calling the function itself, and not on different and later function calls. But if there are notable performance differences those should be valued higher as we cannot assume it's not important for such a basic library.
@kjetilkjeka thanks for the info. That sounds right, and, fwiw, (2) there is pretty nice. My thought is to make the display trait implementations consistent with the rest (add a call to mask
); a potential refactor to shift consistency to "write" could be something to consider in the future.
Musing on this, both mask-on-read and mask-on-write could have their merits. Only when we start handing out guarantees to the compiler on inhabitation we must mask-on-write.
It's certainly practical that so far this is an implementation detail we can play with.
Today I noticed the following code:
prints
2
, which is an invalid value foru1
.I looked at the implementation of ShlAssign and saw:
I think the mask call should be after the
shl_assign
call (or, perhaps, in both places)? If that sounds right, happy to open a PR. There are unit tests forShlAssign
, but none involve shifting beyond the type's 'size'.EDIT: Updated the title of this ticket after discovering the current strategy is to mask on read, so the issue isn't with
ShlAssign
, but actually with the various display traits. See discussion below.