An integer range type like 0..100
means that a value can be any integer in that range. Arithmetic operations on range types produce other range types, for example if a: 10..20
and b: 5..7
then a + b: 15..27
. (Each range here includes both endpoints, for the sake of the example.) To be clear, here 0..100
is a type, not a value; the values are integers in that range.
Checking if a number is in a range can be treated as a fallible type conversion, e.g. converting x
to type 0..100
could return an optional value which is None
if x
is not in that range. Alternatively, it can be treated as a type predicate, so that if(x in 0..100) { ... }
narrows the type of x
within the conditional branch.
Range types are used in a few languages, notably Ada (which only has integer types with defined ranges). They have some benefits, such as eliding bounds-checking for arrays of known length, avoiding overflows by using wide enough integers for any operation, and preventing division-by-zero errors. They also seem relatively easy to support, unlike dependent types which are more powerful and can represent types like 0..n
where n
isn't a compile-time constant. So what are the reasons why most statically-typed languages don't support integer range types?
0..4294967295
, but they don't provide the advantages of letting the user specify that range arbitrarily, and they don't prevent overflows or arithmetic errors. That said, the main use-cases are for integers which are logically bounded (like an index into a constant array), not integer values which theoretically ought to be unbounded but must be bounded for practical reasons (like a counter for the number of users). $\endgroup$grid[x + 9 * y]
wheregrid
is an array of length 81 in a sudoku program, where bounds-checking should be unnecessary becausex
andy
are of type0..8
. $\endgroup$(T1: a..b) + (T2: c..d) == (T3: (a+c)..(b+d))
, but does every user of a range type want that behaviour instead of bound-checking that it stays within T1 or T2? In a template language like C++ it's fairly easy for the user to implement those kinds of types and you don't risk imposing unwanted semantics. $\endgroup$a..b
, but the arithmetic expression they write doesn't logically have a result that is necessarily in that range, then they need to do a bounds check before they can assign that value back to a variable of typea..b
. So the semantics of(a+c)..(b+d)
also allow the alternative semantics in a straightforward way, and both ways require the user to handle the condition when the arithmetic result is out of bounds. That said, if there are with languages with more restrictive range types like you describe, answers could mention them. $\endgroup$Range
values. So in Rust0..100
is a value but it is not a type. $\endgroup$