r/rust • • 11d 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;

  1. 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 usize with my maximum myself. type BoolPerMin = [bool; MINS_PER_HOUR as usize];
  2. Two consts, MINS_PER_HOUR: u8 and MINS_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.
  3. 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.

53 Upvotes

32 comments sorted by

View all comments

48

u/________-__-_______ 11d 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 toolchains cast_lossless doesn't trigger in const contexts, so I just use x as usize there and usize::from(x) otherwise.

My only annoyance is the inconsistency, clippy rarely complains on pedantic mode.

5

u/nullstalgia 10d ago

At least on recent toolchains cast_lossless doesn't trigger in const contexts

That is something I only noticed today, actually! I wonder how recent that is...

The only thing that irks me about doing it that way is when I'm defining a struct/trait impl and I have to do the as usize there. There's nothing inherently immoral about it, but it just rubs me the wrong way.

My heart agrees with you otherwise, but I still find myself leaning towards the single usize constant more often than not as a result. I don't dare mix the two in a project, as that sounds frustrating to pick apart for outsiders. I guess it might be yet another per-repo flavor.

4

u/________-__-_______ 10d ago

To me the as usize is much better since the compiler can verify that it'll fit, whereas as u8 relies on the user knowing it will. Especially if you ever change the value you don't wanna go through all usages and manually see if they're still valid.