Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-565 · Case file with fixtures · Case 537 of 694 · Compiler evidence

Rust Exclusive Range Patterns Must Contain at Least One Value

Range pattern endpoints are compile-time classification rules. Use ordered exclusive bounds, inclusive syntax when intended, or remove unreachable categories.

Reviewed
Rust
Rust 1.98.1, edition 2024
Targets
all Rust targets
Profiles
dev, release, test

Direct answer

What this Rust failure means

Why it happens
The compile-time category is an empty half-open interval, often exposing a boundary or generated-threshold mistake.
First discriminating check
Choose ordered exclusive bounds, inclusive syntax when both endpoints belong, or remove a category that is intentionally unreachable.

An exclusive range begins at its lower bound and stops before its upper bound. When both endpoints are equal, no value can match. Rust rejects that impossible range pattern with E0579.

The failing fixture uses 0..0 as a match arm.

Pattern ranges classify at compile time

In a pattern, 0..1 matches zero and not one. The compiler can compare constant endpoints and knows that 0..0 is empty.

The official E0579 page states the rule directly: for an exclusive range pattern, lower must be less than upper.

Rejecting the arm is better than allowing dead classification code that may signal a mistaken boundary.

The repaired range contains exactly zero

The repaired fixture changes the arm to 0..1. Tests check both zero and the excluded endpoint one.

For one exact value, I would normally write 0 =>. The range form is kept here to demonstrate the exclusive-bound rule. Literal patterns are often clearer for isolated protocol codes.

The Reference documents range patterns, including permitted bound types and emptiness checks.

Inclusive syntax expresses both endpoints

0..=0 is a non-empty inclusive range matching zero. This may be the intended repair if both written endpoints belong to the category.

I do not switch .. to ..= automatically. It changes the upper boundary for every non-empty range too. In bucket logic, that can create overlaps between adjacent arms.

I write boundary tests for lower minus one when representable, lower, upper minus one, upper, and upper plus one. Those tests reveal inclusive/exclusive misunderstandings quickly.

std::ops::Range represents start..end in expression contexts and is commonly iterated. Its Range documentation describes the half-open interval.

Patterns use dedicated grammar and compile-time bounds, but the half-open intuition transfers. I still verify context because not every range expression capability is valid as a pattern.

Generated boundaries need validation before code generation

A schema generator may emit range arms from configuration. Empty categories can arise when two thresholds become equal or are sorted incorrectly.

I validate boundaries in the generator and report the domain names that conflict. Waiting for rustc E0579 is a useful safety net, but a generator error can explain whether “low” and “medium” were configured inconsistently.

Generated tests should include every transition point, not only one representative in each expected bucket.

Unreachable categories may indicate an empty business state

Sometimes the empty range is not a typo: configuration says a category currently has no members. Encoding that as an impossible match arm adds no behaviour. I omit the arm or represent the category as data if its emptiness must be reported.

The type system should describe possible states, while monitoring or configuration models can describe categories that happen to be empty today.

Character ranges follow ordering too

Range patterns can classify supported scalar domains such as characters. Unicode code-point order is not alphabetical order for every language. A compiling 'a'..='z' pattern is an ASCII-oriented rule, not universal text classification.

I make domain assumptions explicit rather than treating range syntax as locale-aware semantics.

Exhaustiveness and emptiness answer different questions

An exhaustive final _ arm can cover every runtime value while an earlier range remains empty. Exhaustiveness therefore does not validate that each named category is useful. I test category reachability independently and prefer named constants for thresholds. If a threshold change collapses a bucket, the test should explain which category disappeared. This matters in rate limits and protocol versions where an unreachable arm may hide a rollout configuration error even though the overall match stays exhaustive and safe.

My E0579 checklist

  • Is the pattern exclusive or inclusive?
  • Is the lower endpoint strictly less than the exclusive upper endpoint?
  • Would one literal express a single value more clearly?
  • Does changing to inclusive syntax create overlap with another arm?
  • Are boundaries generated from sorted and validated configuration?
  • Do tests cover values on both sides of every endpoint?
  • Should an empty business category be omitted rather than matched?
  • Does numeric or character order match domain meaning?

The core principle is that a range pattern is a precise set of values. I make that set non-empty, test its edges, and avoid using endpoint syntax as an approximate category description.