Has it been considered whether some kind of bound makes sense? I guess back-compat blocks a hard requirement, but it feels weird to me that let x = f64::NAN..f64::NAN; is totally fine. What does that range even mean? (The docs just refer to contains, which that value can’t instantiate.) Part of my brain wants it to be a “valid range” in an Elements of Programming iterator range sense, so it’s not PartialOrd. Maybe it’s some kind of often-only-checked-in-debug SameSubdomain? A place to hook a “yes, they’re both linked list iterators, but they’re into different lists” assert would be nice.
FWIW, here’s Dijkstra 831 on range conventions recommends only using what we have as ..:
The programming language Mesa, developed at Xerox PARC, has special notations for intervals of integers in all four conventions. Extensive experience with Mesa has shown that the use of the other three conventions has been a constant source of clumsiness and mistakes.
Using that to say “no inclusive ranges” is probably overstating it, especially given the parallels to patterns. But I’m definitely not convinced that 0<..=10 is something people would realistically use. (Though maybe having it internally as a better way to reverse would be useful? It’s a nice description of how you’d produce 10, 9, …, 1.)
One API I’d like to see, at least as a thought experiment, is split. I think for our current Range, it’s fairly obvious, and makes sense in a context like using a Range for binary search bounds: (a..b).split(k) => (a..k, k..b) (Maybe with some kind of check that k ∈ [a, b]?)
I think that does fit best for most things (merge sort, binary search, …) since it gives full coverage with no overlap. I assume that’s the root of the EWD statement. (Of course this is also true for half-closed ranges, but we’re using [0,n) as our indexing strategy. If this was Lua, Fortran, ALGOL 68, etc, then half-closed (0,n] could arguably be the “best” choice.)
The others are a bit odd from a divide-and-conquer perspective. Fully-open (a, b) => (a, k) (k, c) means that k isn’t in either range. The only algorithm I can come up with that kind of step is single-pivot binary-comparison partition, where one of the elements is in the right place and you don’t need to look at it again. (Of course the good quicksorts all use a more complex partition…)
Then fully-closed [a, b] => [a,k] [k, c] means that k is in both sides. I struggle to think of an algorithm that wants to look at the item again on both sides. The best I could come up with is splitting curves into smaller curves, but I think that’s actually a property of curves usually being given ends of a dense number type. If I make a complete butchery of maths, [0i32, 1i32) and [0f64, 1f64) both have “size” 1, but [0i32, 0i32] and [0f64, 0f64] have “sizes” 1 and 0 respectively. (Relatedly, the range [f64::consts::PI, f64::consts::PI] doesn’t actually contain π, but does contain something not-π. Either PI..succ(PI) or pred(PI)..PI–I’m not sure which–does at least contain π. Or course, making that kind of range useful leads to interval arithmetic, so I’m going way off course. Unless that’s the only rigorous way to decide how to handle a new Range?)
As a bit of a probably-not-actionable aside, I’m not really a fan of many of the “there’s no value outside the range” examples I’ve seen. MON…FRI seems reasonable initially, but then you try FRI…MON for the long weekend, and it silently does what you obviously didn’t want. (See trait bounds above.) Anything using ‘a’…‘z’ also feels like a unicode-support antipattern. On the other hand, I can totally understand wanting to be able to iterate the full range of u8…
(Sorry that this thread is a bit old; I got here from an issue and am not sure where is best to comment.)