Repository navigation
Closed
Labels
A-diagnosticsArea: Messages for errors, warnings, and lintsArea: Messages for errors, warnings, and lintsA-exhaustiveness-checkingRelating to exhaustiveness / usefulness checking of patternsRelating to exhaustiveness / usefulness checking of patternsA-patternsRelating to patterns and pattern matchingRelating to patterns and pattern matchingC-bugCategory: This is a bug.Category: This is a bug.E-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.E-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.T-langRelevant to the language teamRelevant to the language team
Description
usize ranges in match statements are surprising:
fn test(i: usize) {
match i {
0..=usize::MAX => todo!(),
}
}I expected this to compile; instead, rustc returns an error:
error[E0004]: non-exhaustive patterns: `usize::MAX..` not covered
--> src/lib.rs:2:11
|
2 | match i {
| ^ pattern `usize::MAX..` not covered
|
= note: the matched value is of type `usize`
= note: `usize` does not have a fixed maximum value, so half-open ranges are necessary to match exhaustively
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
3 ~ 0..=usize::MAX => todo!(),
4 ~ usize::MAX.. => todo!(),
|
For more information about this error, try `rustc --explain E0004`.
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 "usize does not have a fixed maximum value" when usize::MAX is right there!
Doing a little digging, it looks like it dates back to at least #118598
Meta
rustc --version --verbose:
rustc 1.89.0 (29483883e 2025-08-04)
binary: rustc
commit-hash: 29483883eed69d5fb4db01964cdf2af4d86e9cb2
commit-date: 2025-08-04
host: aarch64-apple-darwin
release: 1.89.0
LLVM version: 20.1.7
Metadata
Metadata
Assignees
Labels
A-diagnosticsArea: Messages for errors, warnings, and lintsArea: Messages for errors, warnings, and lintsA-exhaustiveness-checkingRelating to exhaustiveness / usefulness checking of patternsRelating to exhaustiveness / usefulness checking of patternsA-patternsRelating to patterns and pattern matchingRelating to patterns and pattern matchingC-bugCategory: This is a bug.Category: This is a bug.E-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.E-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.T-langRelevant to the language teamRelevant to the language team
Activity
workingjubilee commentedon 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.fmease commentedon Sep 12, 2025
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.
fmease commentedon Sep 12, 2025
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?workingjubilee commentedon Sep 14, 2025
seems like a clear diagnostic bug, tbh, even if it is mostly going to be about choosing different words.
workingjubilee commentedon 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
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. ↩
hkBst commentedon Sep 19, 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?
fmease commentedon Sep 19, 2025
Was my explanation not sufficient?
fmease commentedon Sep 19, 2025
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...hkBst commentedon Sep 19, 2025
@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?
workingjubilee commentedon Sep 24, 2025
@Nadrieril vibes?
fmease commentedon Sep 25, 2025
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.
Rollup merge of rust-lang#146745 - helldawg:master, r=workingjubilee
Rollup merge of rust-lang#146745 - helldawg:master, r=workingjubilee
Unrolled build for #146745
hkBst commentedon Sep 28, 2025
The clarification of the diagnostic is actually very satisfactory.
Nadrieril commentedon Oct 3, 2025
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:
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.