Critic notes about Swift Range types jungle

Open-ended ranges are interesting. In fact I seemingly didn't know Swift had them, until this thread - or if I did learn of them when they were added years ago, I forgot. The number of times I explicitly wrote n..<(.endIndex) in the interim, ugh! :laughing:

That they crash if you exceed the actual bounds of the underlying type is consistent with integer arithmetic, where overflow also crashes. Both in terms of the behaviour and conceptually - both are trying to pretend that numeric types have infinite range, and fail at runtime if that turns out to not be reality ("whoops").

For arithmetic you have wrapping versions which don't crash. I suppose the equivalent for 'open-ended' ranges is actually n...(.max).

It makes me a bit sad that the default (and vastly most common) numeric manipulations in Swift are unsafe (where I use unsafe in the sense of "crashes and destroys user data" rather than merely "undefined behaviour").

However, I suspect it's not worth trying to address this for open-ended ranges alone - it feels like it should be tackled (separately) as a broader effort to make numerics safer in Swift.

There's a lot ground (for me) to cover, thanks for the links provided above by @Lantua and others. The more I'm looking at them the more different axis of range types split apart. Float/BigNum/Int – and ranges made of them – three very different things. Unbound vs Bound ranges – very different things. As was questioned by @Drew_Crawford1 it might worth to merge open and closed range (at least for the integer subtypes). My intuition (which might not reflect that of a typical swift user) fails on Swift ranges as I came from an assumption that range is a very primitive type (like nothing more than a pair of numbers, possibly with a few more bits of information), and assumptions that things like "reverse" just trivially exchange the numbers (while in fact "reverse" does something very dramatic). The unbounded ranges mean different things in different contexts ("for in" which doesn't stop at UInt8.max vs "string[start...]" that stops at string end). Thanks for the references to ranges in other languages, worth looking into that.

My 2 cents on this particular note:

You are making (already made) a frozen type that's very hard to change in the future. Even if we don't see a pressing need to introduce left open ranges today (other than for the sake of symmetry / consistency) such need might become pressing in the future – but by then the type is "too frozen" to be able supporting it.

That’s more-or-less backwards. Swift can add a new range type any time (and third parties can also define them), precisely because there’s a different type for each, rather than a single type that purports to represent all ranges.

9 Likes

one thing i like about the distinction between Range<T> and ClosedRange<T> is that the latter is statically known to always contain at least one element of T.

1 Like

Indeed, this is one of the complaints folks have about rust’s RangeInclusive type. It includes an extra bool that must be checked by some operations even though the emptiness of an inclusive range ought to be impossible by construction.

(This is caused by ranges in rust being iterators, rather than because it’s a separate type – it is still a separate type in rust)

2 Likes

This would be no small undertaking (and I personally don't have the skillset to actually do it). That said, is it perhaps the case that there is a sufficient body of swift-centric material such that ChatGPT-class technology could be applied to generate (initiate?) such summaries?