Avoiding runtime exception with empty range

pub fn main() {
    let t = vec![0, 1, 2, 3, 4, 5];
    let f = &t[8..7];
    println!("t: {:?}", t);
}

In this case, I expect to just have f = &[], but instead Rust playground throws an exception.

The 8..7 looks silly but in practice is a result of calculations. Is there a way to have x..y where x>=y to just behave like the empty set ?

Yes, 0..0 is always valid. In general n..n is a valid empty slice of x if n <= x.len()

    let f = t.get(8..7).unwrap_or_default();

But this may be surprising when only the end is OOB.

    let f = &t[t.len().min(8)..t.len().min(7)];

Clamps more intuitively(?).

Actually you want

        let end = self.len().min(7); // 7 == range.end
        let start = end.min(8); // 8 == range.start

Because 1..0 will panic even if 1 is in range.

Whichever approach you choose, you can then implement Index<YourNewType> (and IndexMut) for for ergonomic purposes.

1 Like

In retrospect, I do not think I phrased this question very well.

I would like a world where (x .. y) is NOT an exception for (x > y), and &blah[x .. y] is &[] instead of exception when [x..y] is empty set.

This would make lots of math with ranges be convenient (i.e. it hits valid value of empty range / slice instead of exception).

An interval (a, b) where a>b is ill-defined, and so are such ranges. Treating it as empty would hide potential problems or bugs. It's probably better that you fix the calculation to be consistent instead of hiding the problem cosmetically.

1 Like

To add to the other comments, the proposed semantics are also slower to implement (they require clamping the indexes, which is currently not required) and would be a breaking change.

I believe [a, b) is defined as {x | a <= x && x < b}, which is well defined empty set for a > b.

In any context in which I've seen intervals defined, it's always specified that a<=b. Regardless, the docs actually say explicitly:

It is empty if start >= end .

So you're right, I think this is inconsistent with the behavior of slice indexing, and that should at least be reflected in the docs. Fixing the behavior would unfortunately be a breaking change as noted.

2 Likes

I'm a bit ambivalent about indexing with a start>end ranges giving an empty slice. On one hand, it is unquestionable that start>end ranges are valid empty ranges and I agree that being able to index using them is sometimes convenient. On the other hand, many other times it's a sign of a logic error somewhere, and should not be silently accepted. And in Rust we're expected to be explicit when we do something that looks suspicious. Maybe Range<usize> should have a method that turns a "suspicious empty slice" into an always-valid one (which is to say, 0..0).

I'm more certain that ranges where start==end and start > slice.len() should not be valid indexes. It's at least as likely, and probably much more likely, to be an accidental bug waiting to blow up rather than something intentional.

By the way, an interesting detail is that 3..=2 is a valid slice index (assuming len()>=3) and denotes an empty range. 3..=1 is not.

1 Like