Repository navigation
Matching on usize requires half-open range #146476
Description
Activity
- addedneeds-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triagingThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
on Sep 12, 2025 - addedT-langRelevant to the language teamRelevant to the language teamC-discussionCategory: Discussion or questions that doesn't represent real issues.Category: Discussion or questions that doesn't represent real issues.A-patternsRelating to patterns and pattern matchingRelating to patterns and pattern matchingA-exhaustiveness-checkingRelating to exhaustiveness / usefulness checking of patternsRelating to exhaustiveness / usefulness checking of patternsand removedC-bugCategory: This is a bug.Category: This is a bug.needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triagingThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
on Sep 12, 2025 This is an interesting quirk of the way the type is defined. Technically, the compiler has no special knowledge of the
usize::MAXconstant: it just is a value. The fact that it will, across all compilation sessions, mystically always be the largest value ofusize(well, unless the stdlib is implemented incorrectly) is a mystery to the compiler.Reacted by León Orell Valerian Liehr and NadrierilDoing a little digging, it looks like it dates back to at least #118598
I mean the PR description basically explains why it is the way it is and I don't see how we would 'fix' this in a principled and performant manner.
Of course, we could slap some
#[lang]or#[rustc_*]attr on theMAXconst in the stdlib but is that really principled? Unless we somehow track this metadata in the const value itself (similar to initializedness and provenance I guess; probably extremely expensive to maintain), this wouldn't be "closed under" normalization (evaluation). ConsiderMY_MAXset tousize::MAX - 1 + 1,(usize::MAX,).0, etc.And if it "doesn't work with" normalization, it would just be a super weird special case that's equivalent to just using a half-open range.
In any case, there might be merit in clarifying that diagnostic note even further.
It is unfortunate but not world shattering imo. In my mind, it compares to constructs like
loop {}chosen overwhile KNOWN_TO_EVAL_TO_TRUE {}as a more restricted version of another construct that allows for more static guarantees. Does that make sense?- addedA-diagnosticsArea: Messages for errors, warnings, and lintsArea: Messages for errors, warnings, and lintsC-bugCategory: This is a bug.Category: This is a bug.and removedC-discussionCategory: Discussion or questions that doesn't represent real issues.Category: Discussion or questions that doesn't represent real issues.
on Sep 14, 2025 seems like a clear diagnostic bug, tbh, even if it is mostly going to be about choosing different words.
Reacted by Matt Keeter- addedE-easyCall for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue.Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue.
on Sep 14, 2025 This just requires updating the text here:
rust/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
Lines 1276 to 1286 in 52618eb
if ty.inner() == cx.tcx.types.usize { err.note(format!( "`{ty}` does not have a fixed maximum value, so half-open ranges are \ necessary to match exhaustively", )); } else if ty.inner() == cx.tcx.types.isize { err.note(format!( "`{ty}` does not have fixed minimum and maximum values, so half-open \ ranges are necessary to match exhaustively", )); } Word choice is going to be the greatest obstacle here, so it requires fairly little programming knowledge to do the update1 but it does require very thoughtful wordsmithing.
Footnotes
-
The process of committing the change, running tests, updating those, opening a PR, and rebasing it after review will admittedly require a little more. ↩
-
- addedE-mentorCall for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion.Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion.
on Sep 14, 2025 I'm not following why the compiler does not or cannot know that
0..=usize::MAXis exhaustive for matching on a usize.Surely the compiler knows the target ABI which specifies the range of usize for the current target, and surely the compiler knows the value of usize::MAX (it's a constant with a different value for each target, through the magic of conditional compilation, right?), so surely the compiler has enough information to deduce exhaustiveness for the current target. Right?
Was my explanation not sufficient?
If the compiler sees
usize::MAXit looks like any other user-defined constant. It just so happens to have the correct max value for each supportedtarget_pointer_widthviacfgs which expand very early on during the compilation process.We still want to treat
0usize..=MY_MAX_CONSTas non-exhaustive whereMY_MAX_CONSTis a constant set to a (concrete and non-portable) usize corresponding to the logical maximum. There's no principled way of letting the compiler know if that value is portable or not because thecfgsystem is only slightly better than a preprocessor.As I've already explained above, we could make
std'susize::MAXspecial by slapping an internal marker attribute on each of its (per target pointer width) definitions. Now, how would the compiler then recognize it?Assuming the compiler doesn't try to to evaluate/normalize the const paths before exhaustiveness/relevancy checking (I don't remember), then the simplest and most unprincipled solution would be to look for said marker attribute on the definition "corresponding to" the unevaluated constant beforehand and if present suppress this error (simplifying).
As I've alluded to above already, what about
0usize..=Kthen whereconst K: usize = usize::MAX;? That wouldn't work with the impl I described above. And even if you were to hack it together and made it work, what aboutKset tousize::MAX + 0;? Every const op would need to recompute "is this logical max"? That's too expensive and unmaintainable.Okay, what if we didn't care about normalization and only accepted the simplest case? Then that would still require a language change, a hard-or-annoying-to-describe-in-spec-lang one at that. And what does it buy you? We already have
0usize...@fmease thanks! My thinking was, there are already things the compiler only checks for the current target arch, so this could be one more. But from your explanation, it seems like the exhaustiveness check is trying to be target arch-independent. It seems the only difference between these two cases is when a manual upper bound with a value equal to usize::MAX is specified. If you go arch-dependent, then we can accept the given code, and if arch-independent, then it's problematic. So why not do this check arch-dependently?
@Nadrieril vibes?
So basically reverting the decision made by Nadrieril's PR? Idk, open a PR and lang-nominate it, I don't have an opinion on this.
- added a commit that references this issue
on Sep 28, 2025 - added a commit that references this issue
on Sep 28, 2025 - added a commit that references this issue
on Sep 28, 2025 The clarification of the diagnostic is actually very satisfactory.
it seems like the exhaustiveness check is trying to be target arch-independent
That's exactly what's happening. For context, removing a feature gate doesn't require input from t-lang so not sure what t-lang thinks of this today. When I removed it I just assumed there was a good reason for having this special behavior but I didn't really question it. And this behavior was added in 2018; I feel like we've matured in our view of portability since.
At this point I think this feature isn't particularly helpful. There's no way someone accidentally writes this and acts surprised if it fails to compile on a different target:
const MY_USIZE_MAX: usize = u64::MAX as usize; // or const MY_USIZE_MAX: usize = 18446744073709551615; match ... { 0..MY_USIZE_MAX => ..., }
which is what this special case is supposed to be for. Plus it requires a bit of weirdnesses in the implementation.
I would approve a PR that reinstates the
precise_pointer_size_matchingfeature gate and help with the t-lang discussions, if anyone's motivated.Reacted by Matt Keeter
usizeranges inmatchstatements are surprising:I expected this to compile; instead,
rustcreturns an error:This is obviously a known edge case, given the custom error message (which is great!), but it's still a weird paper cut. It's especially weird to declare "
usizedoes not have a fixed maximum value" whenusize::MAXis right there!Doing a little digging, it looks like it dates back to at least #118598
Meta
rustc --version --verbose: