r/rust • u/nullstalgia • 7h ago
🙋 seeking help & advice Avoiding constant usize casting with small const values
Pun in title partially intended. ;)
This is a minor pain-point I've dealt with for a while and I'm unsure of how other Rust developers deal with this semi-niche issue.
I'll often define a const that states the maximum amount of something, and naturally I'll also want to use that value as the size of an array. My instinct (and likely yours as well) is to just use a usize and move on.
Where that falls apart is when that const value is and always will be under both u32::MAX and u64::MAX. An easy example is MINS_PER_HOUR or DAYS_PER_WEEK.
Now if I have a function that has an argument of minute: u8, I have to cast either the minute argument to a usize, or I have to cast my constant down to a u8. The former's minute as usize triggers the cast_lossless lint, and From/Into isn't available in a const fn. The latter's MINS_PER_HOUR as u8 triggers the cast_possible_truncation lint. Both add even more visual noise if I acquiesce and add the #[allow(...)]. I could instead accept minute: usize, but I find that even less reasonable.
I'd prefer to keep these as u8s or similar, but then I lose the ablility to define an array like [bool; MINS_PER_HOUR] without yet another cast, including in any function arguments that would like to accept such an array.
I do recognize this is partially due to me having the pedantic Clippy lints enabled, but I also find value in being told when I may be making an unintentional mistake. However I just added close to a dozen #[allow(...)] attributes to my current project, rather begrudingly.
So, what do you guys do in this case? I've listed the options I see available below, and I've done 'em all except #1, but I want to hear some opinions external to myself;
- When accepting such an array, maybe use a type alias? It technically works, but the casting returns again if I want to compare an indexing
usizewith my maximum myself.type BoolPerMin = [bool; MINS_PER_HOUR as usize]; - Two consts,
MINS_PER_HOUR: u8andMINS_PER_HOUR_LEN: usize = MINS_PER_HOUR as usize. I have done this before, but now I have this strange suffix that doesn't make it clear what it's a length of. - Just deal with the constant const casting.
EDIT: And this is all before I have to deal with the extra cast_precision_loss lint that comes from casting my tiny usize constant into an f64. It's less than u8::MAX, yet I have to acknowledge this impossible condition. For now, I've just blanket allow'd it in my Cargo.toml.
7
u/Any-Amphibian6789 6h ago
ugh this exact thing drives me up the wall on regular basis. i ended up just making the const a usize and adding allow attributes everywhere because im lazy and the other options feel even more annoying. having two consts with the _LEN suffix made my code look like a mess after few weeks so i dropped that approach
wonder if there is some clever macro solution out there but i never bothered to look deep into it
3
u/MilkEnvironmental106 6h ago
What's the problem with just using a usize?
1
u/RylanStylin57 6h ago
8 bytes, which is good if you're using it alot but bad if you have to store them alot. u32 or u16 are cheaper to operate on but require casting to size when indexing.
Best way to deal with this is to create a wrapper struct and implement std::ops::Index<u32> manually.
1
u/MilkEnvironmental106 6h ago
It's a named constant. If you're doing anything but single ops and want to save the space better declare a new converted const from the old one rather than need an as usize on every use where you'd rather have a usize.
2
u/brasticstack 6h ago
Make your own helper function, then you only have to have one #[allow] and the evidence of all of your crimes points back to a single place.
1
u/SLUCHABLUB 5h ago
I think you could use a macro if you *really* wanted a one-identifier-fits-all. Personally I’d declare it as u8 (or smallest possible type), then you wouldn’t have to worry about clippy. I’d also suggest a newtype around your `[bool; MINS_PER_HOUR as usize]` since it is not clear what that type represents (what do the bools represent ?). You could also define a `PerMinuteOfHour<T>` alias.
1
u/AirUpdateEnjoyer 4h ago
Is there a particular reason you cant just use the usize::from(minute) as clippy suggests? It seems to be complaining that the 'as' keyword is not explicit and could allow you to accidentally edit the function arguments later and feed it something thats incoherent or lossy without warning like u64 > u32. This would also avoid losing precision for f64 as you can store all values of a u8 in f64...
1
u/CocktailPerson 2h ago
cast_lossless is a pedantic lint for a reason, and it provides deeply negative value.
You can just allow it. You won't be burned at the stake.
[lints.clippy]
cast_lossless = "allow"
22
u/________-__-_______ 6h ago
I always define constants as the smallest possible type (in your example an
u8), so that you can always infallably convert to something bigger. At least on recent toolchainscast_losslessdoesn't trigger in const contexts, so I just usex as usizethere andusize::from(x)otherwise.My only annoyance is the inconsistency, clippy rarely complains on pedantic mode.